zoukankan      html  css  js  c++  java
  • android注解入门 并来自己写一个框架

    介绍

    这里我带大家来学习一下注解 并且用来写下一个模仿xUtils3 中View框架
    此框架 可以省略activity或者fragment的 findViewById 或者设置点击事件的烦恼
    我正参加2016CSDN博客之星的比赛 希望您能投下宝贵的一票,点击进入投票
    我的github上的源码,包含doc和使用说明

    如下代码:

    fragment

    package a.fmy.com.myapplication;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    import a.fmy.com.mylibrary.FmyClickView;
    import a.fmy.com.mylibrary.FmyContentView;
    import a.fmy.com.mylibrary.FmyViewInject;
    import a.fmy.com.mylibrary.FmyViewView;
    
    //你的fragment的布局id  Your fragment's LayoutId
    @FmyContentView(R.layout.fragment_blank)
    public class BlankFragment extends Fragment {
        //你想实例化控件的id
        //Do you want to control instance id
        // 等价于 findViewByid
        //Equivalent to the findViewByid
        @FmyViewView(R.id.tv1)
        TextView tv1;
        @FmyViewView(R.id.tv2)
        TextView tv2;
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
           //初始化fragment Initialize Fragement
            return FmyViewInject.injectfragment(this,inflater,container);
        }
        //你想给哪个控件添加 添加事件 的id
        //Do you want to add add event id to which controls
        @FmyClickView({R.id.tv1,R.id.tv2})
        public void myOnclick(View view){
            switch (view.getId()) {
                case R.id.tv1:
                    tv1.setText("TV1  "+Math.random()*100);
                    break;
                case R.id.tv2:
                    tv2.setText("TV2  "+Math.random()*100);
                    break;
                default:
    
            }
    
        }
    }

    Activity

    package a.fmy.com.myapplication;
    
    import android.os.Bundle;
    import android.support.v4.app.FragmentTransaction;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.FrameLayout;
    import a.fmy.com.mylibrary.FmyContentView;
    import a.fmy.com.mylibrary.FmyViewInject;
    import a.fmy.com.mylibrary.FmyViewView;
    
    @FmyContentView(R.layout.activity_main)
    public class MainActivity extends AppCompatActivity {
    
        @FmyViewView(R.id.fl)
        FrameLayout fl;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //initActivity
            // 初始化activity
            FmyViewInject.inject(this);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.fl,new BlankFragment());
            fragmentTransaction.commit();
        }
    }
    

    java注解学习

    java注解教学大家点击进入大致的看一下即可 不然我不知道这篇博客需要写多久

    activity设置填充布局框架

    这里我们先写一个用于activity框架 你学习完了之后其实你也会fragment了.
    1. 实现activity不需要调用setContentView(R.layout.activity_main);此方法完成布局填充 我们看下效果
    不使用框架:

    package a.fmy.com.mylibrary;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    使用框架:

    package a.fmy.com.mylibrary;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    @FmyContentView(R.layout.activity_main)
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                FmyViewInject.inject(this);
        }
    }

    第一步:
    创建一个注解类如下
    @Target —>>此注解在什么地方可以使用 如类还是变量
    ElementType.TYPE只能在类中使用此注解
    @Retention(RetentionPolicy.RUNTIME) 注解可以在运行时通过反射获取一些信息(这里如果你疑惑那么请六个悬念继续向下看)

    /**
     * 此方注解写于activity类上 可以免去 setContentView()步骤 
     * @author 范明毅
     * @version 1.0
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FmyContentView {  
        /**
         * 保存布局文件的id eg:R.layout.main
         * @return 返回 布局id
         */
        int value();
    }

    第二步:
    写一个工具类 配合注解使用 当开发者使用此类时激活注解的作用

    public class FmyViewInject {
        /**
         * 保存传入的activity
         */
        private static Class<?> activityClass;
        /**
         * 初始化activity和所有注解
         * 
         * @param obj
         *            你需要初始化的activity
         */
        public static void inject(Object obj) {
        }
    
        /**
         * 初始化activity布局文件 让其不用调用setContentView
         * 
         * @param activity
         */
        private static void injectContent(Object obj) {
        }
    }

    大家先不用着急看不懂为什么这样写原因

    核心源码位于injectContent 我们来实现此方法

        /**
         * 初始化activity布局文件 让其不用调用setContentView
         * 
         * @param activity
         */
        private static void injectContent(Object obj) {
    
            // 获取注解
            FmyContentView annotation = activityClass
                    .getAnnotation(FmyContentView.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();
                }
            }

    此方法写完后工具类的inject()方法调用即可

        /**
         * 初始化activity和所有注解
         * 
         * @param obj
         *            你需要初始化的activity
         */
        public static void inject(Object obj) {
            activityClass = obj.getClass();
            // 初始化activity布局文件
            injectContent(obj);
        }

    完整代码:

    public class FmyViewInject {
        /**
         * 保存传入的activity
         */
        private static Class<?> activityClass;
        /**
         * 初始化activity和所有注解
         * 
         * @param obj
         *            你需要初始化的activity
         */
        public static void inject(Object obj) {
            activityClass = obj.getClass();
            // 初始化activity布局文件
            injectContent(obj);
        }
        /**
         * 初始化activity布局文件 让其不用调用setContentView
         * 
         * @param activity
         */
        private static void injectContent(Object obj) {
    
            // 获取注解
            FmyContentView annotation = activityClass
                    .getAnnotation(FmyContentView.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查找控件

    效果如下

    @FmyContentView(R.layout.activity_main)
    public class MainActivity extends FragmentActivity {
        //直接实例化
        @FmyViewView(R.id.fl)
        private FrameLayout fl;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            FmyViewInject.inject(this);
    
    
        }
        }

    第一步:
    继续写一个注解

    /**
     * 此方注解写于activity类中 控件变量上 可以省去findViewId 的烦恼
     * @author 范明毅
     * @version 1.0
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FmyViewView {
        /**
         * 保存view控件的id
         * @return view控件id
         */
        int value();
    }

    第二步 继续第一节的”activity设置填充布局框架”中的工具类添加新的方法

     /**
         * 初始化activity中的所有view控件 让其不用一个findViewid 实例化
         *
         * @param activity
         */
        private static void injectView(Object activityOrFragment) {
    
            // 对象所有的属性
            Field[] declaredFields = null;
    
    
            // 健壮性
            if (activityClass != null) {
                // 获取du所有的属性 包含私有 保护 默认 共开 但不包含继承等
                // getFields可以获取到所有公开的包括继承的 但无法获取到私有的属性
                declaredFields = activityClass.getDeclaredFields();
            }
    
    
            // 健壮性
            if (declaredFields != null) {
                // 遍历所有的属性变量
                for (Field field : declaredFields) {
    
                    // 获取属性变量上的注解
                    FmyViewView annotation = field.getAnnotation(FmyViewView.class);
    
                    // 如果此属性变量 包含FMYViewView
                    if (annotation != null) {
                        // 获取属性id值
                        int id = annotation.value();
    
                        Object obj = null;
                        try {
    
                            // 获取activity中方法
                            obj = activityClass.getMethod("findViewById",
                                    int.class).invoke(activityOrFragment, id);
    
    
                            Log.e("FMY", "" + field.getClass());
                            // 设置属性变量 指向实例
    
                            // 如果修饰符不为公共类 这里注意了 当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();
                        }
    
                    }
                }
            }
    
        }

    第三步
    在工具类中的inject ()方法调用

        /**
         * 初始化activity和所有注解
         *
         * @param obj 你需要初始化的activity
         */
        public static void inject(Object obj) {
    
            activityClass = obj.getClass();
    
            // 初始化activity布局文件
            injectContent(obj);
    
            // 初始化所有控件实例 省去findViewId的痛苦
            injectView(obj);
    
        }

    activity设置控件的点击事件

    这里需要的知识点 如动态代理等 这里大家可以自己百度看下
    效果如下

    @FmyContentView(R.layout.activity_main)
    public class MainActivity extends FragmentActivity {
    
        @FmyViewView(R.id.fl)
        private FrameLayout fl;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            FmyViewInject.inject(this);
    
    
        }
    
        //当填充的布局中 id为R.id.fl 被点击将调用如下方法
        @FmyClickView({R.id.fl})
        public void onClick(View v){
            Log.e("fmy", "===>>");
        }
    }
    

    第一步 :
    同样写下一个注解

    /**
     * 
     * 设置点击事件的注解 只需要在某方法 上写上此注解即可 如@FmyClickView({R.id.bt1,R.id.bt2})
     * @version 1.0
     * @author 范明毅
     *
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FmyClickView {
        /**
         * 保存所有需要设置点击事件控件的id
         * @return 
         */
        int [] value();
    }
    

    第二步:
    写下一个代理处理类(我写在工具类中)

    /**
         * 代理处理点击逻辑代码
         * 
         * @author 范明毅
         *
         */
        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);
            }
    
        }

    第三步:
    在工具类中写下一个方法用于初始化点击事件

        /**
         * 初始化所有控件的点击事件 只需要某方法上写上对应注解和id即可
         * 
         * @param activity
         */
        private static void inijectOnClick(Object activityOrFragment) {
    
            //获得所有方法
            Method[] methods  = null;
    
    
                 methods = activityClass.getMethods();
    
    
    
            // 遍历所有的activity下的方法
            for (Method method : methods) {
                // 获取方法的注解
                FmyClickView fmyClickView = method
                        .getAnnotation(FmyClickView.class);
                // 如果存在此注解
                if (fmyClickView != null) {
    
                    // 所有注解的控件的id
                    int[] ids = fmyClickView.value();
    
                    // 代理处理类
                    MInvocationHandler handler = new 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();
                        }
    
                    }
    
                }
            }
    
        }

    第四部:
    在工具类的inject()调用即可

    
        /**
         * 初始化activity和所有注解
         * 
         * @param obj
         *            你需要初始化的activity
         */
        public static void inject(Object obj) {
    
            activityClass = obj.getClass();
    
            // 初始化activity布局文件
            injectContent(obj);
    
            // 初始化所有控件实例 省去findViewId的痛苦
            injectView(obj);
    
            // 初始化所有控件的点击事件
            inijectOnClick(obj);
        }
  • 相关阅读:
    [转载]PHP中PSR-[0-4]规范
    Git忽略规则及.gitignore规则不生效的解决办法
    nginx配置tp5的pathinfo模式并隐藏后台入口文件
    php过滤&nbsp;字符
    使用ajax的post方式下载excel
    scws简单中文分词
    php的api及登录的权限验证
    对钩子的理解
    基于角色的权限控制
    微信开发之SVN提交代码与FTP同步到apache的根目录
  • 原文地址:https://www.cnblogs.com/muyuge/p/6152097.html
Copyright © 2011-2022 走看看