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

传统的项目中,为了从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(objid);
            
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(activityOrFragmentid);
                    
// 设置属性变量 指向实例
                    // 
如果修饰符不为公共类 这里注意了 activity
                    // 
控件变量为private的时候 我们去访问会失败的 要么打破封装系 要么变量改为public
                    //
 private TextView tv 这种情况 如果不打破封装会直接异常
                    
if (Modifier.PUBLIC != field.getModifiers()) {
                        // 打破封装性
                        
field.setAccessible(true);
                    
}
                    // 这里相当于 field= acitivity.obj
                    
field.set(activityOrFragmentobj);
                
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(activityOrFragmenti);
                    if 
(view != null) {
                        Method method2 = view.getClass().getMethod(
                                "setOnClickListener",
                                
View.OnClickListener.class);
                        
method2.invoke(viewnewProxyInstance);
                    
}
                } catch (Exception e) {

                    e.printStackTrace();
                
}
            }
        }
    }

}

static class MInvocationHandler implements InvocationHandler {
    //这里我们到时候回传入activity
    
private Object target;
    
// 用户自定义view 的点击事件方法
    
private Method method;

    public 
MInvocationHandler(Object targetjava.lang.reflect.Method method) {
        super();
        this
.target = target;
        this
.method = method;
    
}

    @Override
    
public Object invoke(Object proxyMethod methodObject[] args)
            throws Throwable {
        // 调用用户自定义方法的点击事件 activity调用中开发者设定的方法
        
return this.method.invoke(targetargs);
    
}

}


在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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员一鸣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值