Andorid Widget 使用 RemoteViews 加载自定义View

最终效果图

Android Widget 的实现方式中 View 是由 RemoteViews 实现的


 * 

{@code RemoteViews} is limited to support for the following layouts:

*

And the following widgets:

*
  • {@link android.widget.AdapterViewFlipper}
  • *
  • {@link android.widget.FrameLayout}
  • *
  • {@link android.widget.GridLayout}
  • *
  • {@link android.widget.GridView}
  • *
  • {@link android.widget.LinearLayout}
  • *
  • {@link android.widget.ListView}
  • *
  • {@link android.widget.RelativeLayout}
  • *
  • {@link android.widget.StackView}
  • *
  • {@link android.widget.ViewFlipper}
  • *
  • {@link android.widget.AnalogClock}
  • *
  • {@link android.widget.Button}
  • *
  • {@link android.widget.Chronometer}
  • *
  • {@link android.widget.ImageButton}
  • *
  • {@link android.widget.ImageView}
  • *
  • {@link android.widget.ProgressBar}
  • *
  • {@link android.widget.TextClock}
  • *
  • {@link android.widget.TextView}
  • public class RemoteViews implements Parcelable, Filter {...}

    RemoteViews 并不是一个 View,它继承自 Parcelable,Android 官方支持的 View 上方的少数几种,自定义 View 明显是不支持的

            private Class getParameterType() {
                switch (this.type) {
                    case BOOLEAN:
                        return boolean.class;
                    case BYTE:
                        return byte.class;
                    case SHORT:
                        return short.class;
                    case INT:
                        return int.class;
                    case LONG:
                        return long.class;
                    case FLOAT:
                        return float.class;
                    case DOUBLE:
                        return double.class;
                    case CHAR:
                        return char.class;
                    case STRING:
                        return String.class;
                    case CHAR_SEQUENCE:
                        return CharSequence.class;
                    case URI:
                        return Uri.class;
                    case BITMAP:
                        return Bitmap.class;
                    case BUNDLE:
                        return Bundle.class;
                    case INTENT:
                        return Intent.class;
                    case COLOR_STATE_LIST:
                        return ColorStateList.class;
                    case ICON:
                        return Icon.class;
                    default:
                        return null;
                }
    
    

    它不是一个 View,那我们就不能使用 findViewById 方法来找到对应的 View 来进行操作。
    并且 set 方法也是固定的,所支持传入 setXXX 方法的操作参数只有上面几种。

    解决方案 : 将自定义 View 放到 AOSP (Android Open Source Project) 源代码中运行。

    
    public class RemoteViews implements Parcelable, Filter {...
        /* (non-Javadoc)
         * Used to restrict the views which can be inflated
         *
         * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
         */
        public boolean onLoadClass(Class clazz) {
            return clazz.isAnnotationPresent(RemoteView.class);
        }
    ...
    }
    
    @RemoteView
    public class LinearLayout extends ViewGroup {...}
    @RemoteView
    public class Button extends TextView{
    ...
        @android.view.RemotableViewMethod
        public final void setText(@StringRes int resid) {
            setText(getContext().getResources().getText(resid));
            mTextSetFromXmlOrResourceId = true;
            mTextId = resid;
        }
    ...
    }
    @RemoteView
    public class ImageView extends View  {...
        @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
        public void setImageResource(@DrawableRes int resId) {
        ...}
    ...
    }
    @RemoteView
    public class TextView extends View implements ViewTreeObserver.OnPreDrawListener  {...}
    
    

    在 RemoteView 支持的 View 开头都有个 @RemoteView 的注解,结合 RemoteView 继承了 Filter,
    发现在加载 RemoteView 的时候会去判断这个类时候是否带有专属的注解。所以我们的自定义 View 要加注解才能编译成功。

    @RemoteView
    public class Button extends TextView{
    ...
        @android.view.RemotableViewMethod
        public final void setText(@StringRes int resid) {
            setText(getContext().getResources().getText(resid));
            mTextSetFromXmlOrResourceId = true;
            mTextId = resid;
        }
    ...
    @RemoteView
    public class ImageView extends View  {...
        @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
        public void setImageResource(@DrawableRes int resId) {
        ...}
    ...
    }
    
    public class RemoteViews implements Parcelable, Filter {
    ...
     private MethodHandle getMethod(View view, String methodName, Class paramType,
                boolean async) {
              ...
                        if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
                            throw new ActionException("view: " + klass.getName()
                                    + " can't use method with RemoteViews: "
                                    + methodName + getParameters(paramType));
                        }
                  ...}
    ...}
    

    在 RemoteView 支持的 View 中还发现在对应的set方法中都有加上 @android.view.RemotableViewMethod 的注解,回去查看到源码发现,调用方法的时候也会去判断这个Method 是否加上了专属注解否则无法使用,会提示以下错误。

    2019-02-22 14:41:08.311 2516-2516/com.android.launcher3 W/AppWidgetHostView: 
    Error inflating RemoteViews : android.widget.RemoteViews$ActionException: 
    android.widget.RemoteViews$ActionException: view: android.widget.EnergyMapView
     can't use method with RemoteViews: setLevels(class android.os.Bundle)
    
        /** @hide */
        @android.view.RemotableViewMethod
        public void setLevels(Bundle bundle) {
            Log.i("EnergyMapView", "setBundle");
            setLevels(bundle.getIntArray("xx"));
        }
    

    最后将自定义 View 代码放到 framework 中编译

    @RemoteView
    public class EnergyMapView extends View {...}
    

    将 EnergyMapView 放到 AOSP 目录下的 /frameworks/base/core/java/android/widget/ 路径下,然后开启编译流程,最后在模拟器中运行我们的 APP

    source build/envsetup.sh
    lunch aosp_x86_64-eng
    make update-api
    make -j16
    emulator

    
    
    
        
    
    

    编译完成之后直接在 layout 中引用即可,即便Android Studio 中没有这个类的源码,也不会报错放心

    remoteViews.setBundle(R.id.demo_energy_view, "setLevels", bundle);
    

    最后直接调用即可

    以上的前提是你们公司的设备的 framework 开发是自己人!!!如果不是的话建议将自定义 View 转换成 bitmap 放到 imageView 中。

    你可能感兴趣的:(Andorid Widget 使用 RemoteViews 加载自定义View)