您的位置:首页 > 其它

自定义注解,告别findViewById,你只需要这样做

2017-03-14 15:13 253 查看
传统的项目中,为了从XML文件找到各个控件,findViewById,是不得不去写的代码,以致于太多的控件,太多的findViewId,使我们的代码变得繁琐,获取,强转,千篇一律的重复着某种机制,其实内心也是蛮崩溃的。

当然了,为了解决不必要的findViewById,互联网的世界里也涌现了很多出色的第三方,如ButterKnife,AndroidAnotations,还有XUtils,等等,这些第三方不可否认,是特别的优秀,功能也是非常的强大,使用起来也是非常的简单,但是,也有一定的负面影响,显而可见,这些第三方,不仅仅有注解功能,还有联网,请求数据库等等其它很多功能,而我们只需要一个注解功能,这不等于,我需要一个苹果,你一下给了我一车水果,本来我的胃(内存)就小,这不无形中增加我胃的容量,这不是撑死我吗?其实说的通俗点就是,第三方很多冗余的代码,会占去我们的内存,基于这样的一个原因,不就是一个注解功能吗,我们何不自己实现呢?

下面我们就开始一步步实现吧:

实现注解Activity中的layout

首先我们定义一个接口,用来设置资源layout:

@Target(ElementType.TYPE)//ElementType.TYPE只能在类中使用此注解
@Retention(RetentionPolicy.RUNTIME)//
@Retention(RetentionPolicy.RUNTIME) 注解可以在运行时通过反射获取一些信息
@Documented
public @interface FindViewByIdLayout {

    int value();
}

定义一个工具类,用来初始化布局文件和所在的Activity:

public class ViewUtils {

    /**

     * 保存传入的activity

     */

    private static Class<?> activityClass;

    /**

     * 初始化activity和所有注解

     *

     * @param obj 你需要初始化的activity

     */

    public static void inject(Object obj) {

        activityClass = obj.getClass();

        injectContent(obj);

    }

    // 初始化activity布局文件

    private static void injectContent(Object obj) {

        FindViewByIdLayout annotation = activityClass

                .getAnnotation(FindViewByIdLayout.class);

        if (annotation != null) {

            // 获取注解中的对应的布局id 因为注解只有个方法 所以@XXX(YYY)时会自动赋值给注解类唯一的方法

            int id = annotation.value();

            try {

                // 得到activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码

                Method method = activityClass.getMethod("setContentView",

                        int.class);

                // 调用方法 第一个参数为哪个实例去调用 第二个参数为 参数

                method.invoke(obj, id);

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

}

在所在的Activity里进行调用:

@FindViewByIdLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        ViewUtils.inject(this);

    }

}

实现各个view的注解:

和上面的注解layout步骤一致,首先我们也是定义一个注解View的接口:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FindViewById {

    /**

     * 保存view控件的id

     *

     * @return view控件id

     */

    int value();

}

在上面的ViewUtils工具类中实现注解View的方法:

/**

 * 初始化activity中的所有view控件

 */
private static void injectView(Object activityOrFragment) {

    // 对象所有的属性

    Field[] declaredFields = null;

    // 健壮性

    if (activityClass != null)
{

        // 获取du所有的属性 包含私有 保护 默认 共开 但不包含继承等

        // getFields可以获取到所有公开的包括继承的 但无法获取到私有的属性

        declaredFields = activityClass.getDeclaredFields();

    }

    // 健壮性

    if (declaredFields != null) {

        // 遍历所有的属性变量

        for (Field field : declaredFields) {

            // 获取属性变量上的注解

            FindViewById annotation = field.getAnnotation(FindViewById.class);

            // 如果此属性变量 包含FMYViewView

            if (annotation != null) {

                // 获取属性id值

                int id = annotation.value();

                Object obj = null;

                try {

                    // 获取activity中方法

                    obj = activityClass.getMethod("findViewById",

                            int.class).invoke(activityOrFragment, id);

                    // 设置属性变量 指向实例

                    // 如果修饰符不为公共类 这里注意了 当activity

                    // 控件变量为private的时候 我们去访问会失败的 要么打破封装系 要么变量改为public

                    //如 private TextView tv 这种情况 如果不打破封装会直接异常

                    if (Modifier.PUBLIC != field.getModifiers()) {

                        // 打破封装性

                        field.setAccessible(true);

                    }

                    // 这里相当于 field=
acitivity.obj

                    field.set(activityOrFragment, obj);

                } catch (Exception e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

            }

        }

    }

}

在ViewUtils 工具类inject方法里,添加上述方法,具体使用如下:

@FindViewByIdLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @FindViewById(R.id.tv)

    private TextView mText;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        ViewUtils.inject(this);

        mText.setText("AbnerMing");

    }

}

注解点击事件,和上述步骤一致,写接口,实现其注解方法:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {

    /**

     * 保存所有需要设置点击事件控件的id

     *

     * @return

     */

    int[] value();
}

在上面的ViewUtils工具类中实现注解点击事件的方法:

/**

 * 初始化所有控件的点击事件 只需要某方法上写上对应注解和id即可

 *

 * @param

 */
private static void inijectOnClick(Object activityOrFragment) {

    //获得所有方法

    Method[] methods = null;

    methods = activityClass.getMethods();

    // 遍历所有的activity下的方法

    for (Method method : methods) {

        // 获取方法的注解

        OnClick fmyClickView = method

                .getAnnotation(OnClick.class);

        // 如果存在此注解

        if (fmyClickView != null) {

            // 所有注解的控件的id

            int[] ids = fmyClickView.value();

            // 代理处理类

            ViewUtils.MInvocationHandler handler = new ViewUtils.MInvocationHandler(activityOrFragment,

                    method);

            // 代理实例 这里也可以返回  
  new Class<?>[] { View.OnClickListener.class }中的接口类

            //第一个参数用于加载其他类 不一定要使用View.OnClickListener.class.getClassLoader() 你可以使用其他的

            //第二个参数你所实现的接口

            Object newProxyInstance = Proxy.newProxyInstance(

                    View.OnClickListener.class.getClassLoader(),

                    new Class<?>[]{View.OnClickListener.class}, handler);

            // 遍历所有的控件id 然后设置代理

            for (int i : ids) {

                try {

                    Object view = null;

                    //如果对象是activity

                    view = activityClass.getMethod("findViewById",

                            int.class).invoke(activityOrFragment, i);

                    if (view != null) {

                        Method method2 = view.getClass().getMethod(

                                "setOnClickListener",

                                View.OnClickListener.class);

                        method2.invoke(view, newProxyInstance);

                    }

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }

    }

}

static class MInvocationHandler implements InvocationHandler {

    //这里我们到时候回传入activity

    private Object target;

    // 用户自定义view 的点击事件方法

    private Method method;

    public MInvocationHandler(Object target, java.lang.reflect.Method method) {

        super();

        this.target = target;

        this.method = method;

    }

    @Override

    public Object invoke(Object proxy, Method
method, Object[] args)

            throws Throwable {

        // 调用用户自定义方法的点击事件 让activity调用中开发者设定的方法

        return this.method.invoke(target, args);

    }

}

在ViewUtils 工具类inject方法里,添加上述方法,具体使用如下:

@OnClick(R.id.tv)
public void onClick(View view) {

    Toast.makeText(this, "AbnerMing", Toast.LENGTH_LONG).show();
}

完整代码地址:http://download.csdn.net/detail/ming_147/9780691
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: