android注解入门 并来本身写一个框架

介绍

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

以下代码:java

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注解教学你们点击进入大体的看一下便可 否则我不知道这篇博客须要写多久android

activity设置填充布局框架

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

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);
    }
}

使用框架:github

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) 注解能够在运行时经过反射获取一些信息(这里若是你疑惑那么请六个悬念继续向下看)markdown

/** * 此方注解写于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();
}

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

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 咱们来实现此方法dom

/** * 初始化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()方法调用便可ide

/** * 初始化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);
    }
相关文章
相关标签/搜索