android 注解绑定资源和控件

先来看下以下的一个简单布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btnTest1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />
    <Button
        android:id="@+id/btnTest2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />

    <Button
        android:id="@+id/btnTest3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />
    <Button
        android:id="@+id/btnTest4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />
    <Button
        android:id="@+id/btnTest5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />

    <Button
        android:id="@+id/btnTest6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />


    <Button
        android:id="@+id/btnTest7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test" />
</LinearLayout>

这就是一个布局有放了七个Button.

在Activity中获取,如下:

public class ActivityTest  extends AppCompatActivity  {
    private Button btnTest1;
    private Button btnTest2;
    private Button btnTest3;
    private Button btnTest4;
    private Button btnTest5;
    private Button btnTest6;
    private Button btnTest7;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnTest1 = findViewById(R.id.btnTest1);
        btnTest2 = findViewById(R.id.btnTest2);
        btnTest3 = findViewById(R.id.btnTest3);
        btnTest4 = findViewById(R.id.btnTest4);
        btnTest5 = findViewById(R.id.btnTest5);
        btnTest6 = findViewById(R.id.btnTest6);
        btnTest7 = findViewById(R.id.btnTest7);
    }
    
}

这样要写7次的findViewById,如果还有更多的控件,还要写更多次,那有没有省去findViewById的式?

不过有人会说,可以用ButterKnife,进行控件的绑定,这个也是目前用的比较多的。不过这次以另外的方法来解决这个问题。

先来看下findViewById这个方法:

有没有记得很早之前写findViewById的时候,比如

 btnTest1 = findViewById(R.id.btnTest1)要是在很早之前这样写是会报错的,需要进行类型转换才能正常

 btnTest1 = ButtonfindViewById(R.id.btnTest1)

而现在并不会报错,   

@Override
public <T extends View> T findViewById(@IdRes int id) {
    return getDelegate().findViewById(id);
}

原因就是有返回个泛型。

这个方法返回的是一个继承View的泛型。

我们再来看一个Resources中的方法:

/**
 * Return a resource identifier for the given resource name.  A fully
 * qualified resource name is of the form "package:type/entry".  The first
 * two components (package and type) are optional if defType and
 * defPackage, respectively, are specified here.
 *
 * <p>Note: use of this function is discouraged.  It is much more
 * efficient to retrieve resources by identifier than by name.
 *
 * @param name The name of the desired resource.
 * @param defType Optional default resource type to find, if "type/" is
 *                not included in the name.  Can be null to require an
 *                explicit type.
 * @param defPackage Optional default package to find, if "package:" is
 *                   not included in the name.  Can be null to require an
 *                   explicit package.
 *
 * @return int The associated resource identifier.  Returns 0 if no such
 *         resource was found.  (0 is not a valid resource ID.)
 */
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

name就是要获取资源的名字

defType 资源的类型,如R.layout.activity_main,defType 就是layout,R.id.button,

defType 就是id,

defPackage 就是app的包名。

 

return的返回值就是一个资源所对应的int值,如

public static final int btnOk = 0x7f050025;

 

做如下代码的改动:

package com.example.rxjavatest;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

public class ActivityTest  extends AppCompatActivity  {
    private Button btnTest1;
    private Button btnTest2;
    private Button btnTest3;
    private Button btnTest4;
    private Button btnTest5;
    private Button btnTest6;
    private Button btnTest7;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int btnId = getIdResId(this,getPackageName(),"btnTest1");
        if(btnId > 0){
            btnTest1 = findViewById(btnId);
        }
         btnId = getIdResId(this,getPackageName(),"btnTest2");
        if(btnId > 0){
            btnTest2 = findViewById(btnId);
        }
         btnId = getIdResId(this,getPackageName(),"btnTest3");
        if(btnId > 0){
            btnTest3 = findViewById(btnId);
        }
         btnId = getIdResId(this,getPackageName(),"btnTest4");
        if(btnId > 0){
            btnTest4 = findViewById(btnId);
        }
         btnId = getIdResId(this,getPackageName(),"btnTest5");
        if(btnId > 0){
            btnTest5 = findViewById(btnId);
        }
         btnId = getIdResId(this,getPackageName(),"btnTest6");
        if(btnId > 0){
            btnTest6 = findViewById(btnId);
        }
        btnId = getIdResId(this,getPackageName(),"btnTest7");
        if(btnId > 0){
            btnTest7 = findViewById(btnId);
        }
    }

    // 得到资源id
    public static int getResId(Context context, String pageName, String resName, String type){
        return context.getResources().getIdentifier(resName, type, pageName);
    }

    /**
     * 取得指定名称的资源id
     * @param idName
     * @return
     */
    public static int getIdResId(Context context, String pageName, String idName){
        return getResId(context, pageName, idName, "id");
    }
}

这样也是可以正常获取Button。

我人来现在具体看下

 int btnId = getIdResId(this,getPackageName(),"btnTest1");
 if(btnId > 0){
       btnTest1 = findViewById(btnId);
 }
 如果把Button btnTest1直接转成"btnTest1"就可以不用直接写字符串了。

 如果可以向for循环那样遍历Button定义的变量,这样处理起来是不是也方便是很多了。

 要实现这个两个功能,要用到反射。

 

先看反射中的一些方法:

1.Class用反射获取类中的变量。

public native Field[] getDeclaredFields();

2.Field获取变量的名称:

public String getName()

  1. Field设置变量:

public native void set(Object obj, Object value)

有了这三个方法,就可以做如下的实现

定义一个类叫做ViewUtil

public class ViewUtil {
    /**
     *
     * @param cls 类的名称
     * @param obj 类的实例
     * @param mainView 布局Viwe

     */
    public static void setViewItem(Class<?> cls, Object obj, View mainView){
        Field[] fields = cls.getDeclaredFields();//获取定义的类
        if(fields != null) {
            for (Field field : fields) {
                field.setAccessible(true);//可以访问private变量
                String name = field.getName();//变量的名称
                String declaringClassName = field.getType().getName();//定义的类
                //用于判断是否是View或是View的子类。
                if(!declaringClassName.contains("android.view") &&
                        !declaringClassName.contains("android.widget")){
                    continue;
                }
                if(name == null){
                    continue;
                }
                Context context = mainView.getContext();
                int id = getIdResId(context,context.getPackageName(),name);//通过名称获取id
                if(id <= 0){
                    continue;
                }
                try {
                    View view = mainView.findViewById(id);
                    if(view != null){
                        field.set(obj,view);//给变量赋值
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 得到资源id
    public static int getResId(Context context, String pageName, String resName, String type){
        return context.getResources().getIdentifier(resName, type, pageName);
    }

    /**
     * 取得指定名称的资源id
     * @param idName
     * @return
     */
    public static int getIdResId(Context context, String pageName, String idName){
        return getResId(context, pageName, idName, "id");
    }
}

setViewItem这里不传Activity,而传个View主要是为了通用考虑,像Dialog,悬浮的窗口等,是没有Activity的。

public class ActivityTest  extends AppCompatActivity  {
    private Button btnTest1;
    private Button btnTest2;
    private Button btnTest3;
    private Button btnTest4;
    private Button btnTest5;
    private Button btnTest6;
    private Button btnTest7;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View mainView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
        setContentView(mainView);
        ViewUtil.setViewItem(this.getClass(),this,mainView);
        btnTest1.setText("btnTest1");
        btnTest2.setText("btnTest2");
        btnTest3.setText("btnTest3");
        btnTest4.setText("btnTest4");
        btnTest5.setText("btnTest5");
        btnTest6.setText("btnTest6");
        btnTest7.setText("btnTest7");
    }

    private void btnTest(){

        int btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest1");
        if(btnId > 0){
            btnTest1 = findViewById(btnId);
        }
         btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest2");
        if(btnId > 0){
            btnTest2 = findViewById(btnId);
        }
         btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest3");
        if(btnId > 0){
            btnTest3 = findViewById(btnId);
        }
         btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest4");
        if(btnId > 0){
            btnTest4 = findViewById(btnId);
        }
         btnId =ViewUtil.getIdResId(this,getPackageName(),"btnTest5");
        if(btnId > 0){
            btnTest5 = findViewById(btnId);
        }
         btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest6");
        if(btnId > 0){
            btnTest6 = findViewById(btnId);
        }
        btnId = ViewUtil.getIdResId(this,getPackageName(),"btnTest7");
        if(btnId > 0){
            btnTest7 = findViewById(btnId);
        }
    }
}

这个看起来,获取布局控件是方便多了,但是还是有几个问题:

  1. 如果是自定义的控件,包名都是不确定的,要如何区分是布局的变量还是其他的变量?
  2. 如果控件名称和布局id的名称不一致要怎么处理?
  3. 如果不是控件了,是像String,Color,Dimen这些,也是可以用类似的方法的,但要怎么在一个里做区分?

解决这个几个问题,可以用到运行时注解。

先定义一个enum

public enum FieldType {
    ID(0,"id",""),
    STRING(1,"string","getString"),
    COLOR(2,"color","getColor"),
    DINEN(3,"dimen","getDimension"),
    DINEN_PIXEL_SIZE(4,"dimen","getDimensionPixelSize"),
    ANIM(5,"anim","getAnimation");
    private String defType;
    private int defTypeId = 0;
    private String methodName = "";
    private FieldType(int type,String typeName,String methodName){
        defTypeId = type;
        defType = typeName;
        this.methodName = methodName;
    }

    public String getDefType(){
        return defType;
    }

    public String getMethodName(){
        return methodName;
    }
}

像这个emun中methodName对应的是Resources里对应的方法,比如
public String getString(@StringRes int id) throws NotFoundException,

public int getColor(@ColorRes int id) throws NotFoundException,

public float getDimension(@DimenRes int id) throws NotFoundException,

public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException

public XmlResourceParser getAnimation(@AnimatorRes @AnimRes int id) throws NotFoundException

这些相对应的函数名称。

 

再定义一个注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewField {
    int resId() default -1;//默认ViewId-1
    FieldType fieldType() default FieldType.ID;//哪种类型
}

 

resId默认为-1,表示直接用变量的名称来获取相对资源的值。

fieldType表示是要获取哪类资源的值,默认是获取View的id.

具体的实现如下

/**
 *
 * @param cls 类的名称
 * @param obj 类的实例
 * @param mainView 布局Viwe
 */
public static void setResItem(Class<?> cls, Object obj, Context context,View mainView){
    Field[] fields = cls.getDeclaredFields();//获取定义的类
    if(fields != null) {
        for (Field field : fields) {
            Log.e("cl1"," field is "+field);
            field.setAccessible(true);
            ViewField annotation = field.getAnnotation(ViewField.class);
            Log.e("cl1"," annotation is "+annotation+" fieldName is "+ field.getName());
            if(annotation == null) {
                continue;
            }
            FieldType fieldType = annotation.fieldType();
            int resId = annotation.resId();
            if(resId != -1){
            }else{
                String name = field.getName();//变量的名称
                Log.e("cl"," name is "+name);
                if(name == null){
                    continue;
                }
                String defType = fieldType.getDefType();
                /*
                String defType = "";
                switch (fieldType){
                    case STRING:
                        defType = "string";
                        break;
                    case COLOR:
                        defType = "color";
                        break;
                    case ANIM:
                        defType = "anim";
                        break;
                    case DINEN:
                        defType = "dimen";
                        break;
                }*/
                resId= getResId(context,context.getPackageName(),name,defType);//通过名称获取id
                if(resId <= 0){
                    continue;
                }
            }
            Object res = null;
            Resources resources = context.getResources();
            if(fieldType == FieldType.ID){
                res = mainView.findViewById(resId);
            }else{
                /*
                switch (fieldType){
                    case STRING:
                        res = resources.getString(resId);
                        break;
                    case COLOR:
                        res = resources.getColor(resId);
                        break;
                    case ANIM:
                        res = resources.getAnimation(resId);
                        break;
                    case DINEN:
                        res = resources.getDimension(resId);
                        break;
                }*/
                try {
                    Log.e("cl"," methodName is "+fieldType.getMethodName());
                    Method method = Resources.class.getMethod(fieldType.getMethodName(),int.class);
                    Log.e("cl"," method is "+method);
                    if(method != null){
                        res = method.invoke(resources,resId);
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            try {
                if(res != null){
                    field.set(obj,res);//给变量赋值
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

先看下以的代码:

  String defType = fieldType.getDefType();
  resId= getResId(context,context.getPackageName(),name,defType);//通过名称获取id
还有一种比较简单粗暴的写好,一般大家都能想的到,就是用switch语句:

String defType = "";
switch (fieldType){
    case STRING:
        defType = "string";
        break;
    case COLOR:
        defType = "color";
        break;
    case ANIM:
        defType = "anim";
        break;
    case DINEN:
        defType = "dimen";
        break;
}
resId= getResId(context,context.getPackageName(),name,defType);//通过名称获取id
if(resId <= 0){
    continue;
}

这个能实现相同的功能,但是有个问题是,如果我要加个其他类型的,还是要再加个case语句才能进行扩展,扩展性就变差了。所以选择在emun中定义类型,这样我只需要增加emun中的类型就能进行扩展了。

同样的还有如下的代码:

 

try {
    Method method =    Resources.class.getMethod(fieldType.getMethodName(),int.class);
    if(method != null){
        Resources resources = context.getResources();
        res = method.invoke(resources,resId);
    }
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

也可以写与如下的switc代码:

switch (fieldType){
    case STRING:
        res = resources.getString(resId);
        break;
    case COLOR:
        res = resources.getColor(resId);
        break;
    case ANIM:
        res = resources.getAnimation(resId);
        break;
    case DINEN:
        res = resources.getDimension(resId);
        break;
}

同样也是有扩展性的问题,所以把方法直接定义在了emun中。

 

这样就基本实现了一个比较简单的获取资源和布局的一种方法,但是这种方式,如果是用变量名去获取id值的话,代码混淆的时候,不能把变量给混淆了。

 

gitHub地址:git@github.com:fzhFidt/ResUtil.git

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值