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);
    }
相關文章
相關標籤/搜索