1. 程式人生 > >Android 之實現執行時註解控制元件

Android 之實現執行時註解控制元件

一、首先的說一下註解的分類,

      1、執行時註解,程式碼簡單,複雜性低,但是效率稍微低一點

      2、編譯時註解、程式碼多,結構複雜,但是效率高

這裡介紹的是執行時註解。

二、還是先說一下思路

    首先建立對應的註解類,並且通過反射 findViewById 方法去實現控制元件的註解

    方法的註解使用了動態代理模式,去減少程式碼的量,然後通過反射去呼叫對應的方法。

    反正特別重要的就是反射反射反射

三、實現(這裡主要是添加布局、獲取控制元件、設定點選事件)

    1、添加布局註解

    正常情況下是要呼叫 setContentView的方法設定Activity的佈局檔案,如果使用註解的話也是一句話偷笑

偷笑

    建立註解檔案,設定註解策略為執行時,作用地方為type型別

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    int value();
}

得到當前的類,並且得到註解中的id,然後通過反射 setContentView實現呼叫

  Class<?> aClass = mContext.getClass();
        //獲取到註解類
        ContentView contentView = aClass.getAnnotation(ContentView.class);
        if (contentView != null) {
            //獲取到註解的id
            int layoutID = contentView.value();
            try {
                // 通過反射執行註解方法
                Method method = aClass.getMethod("setContentView", int.class);
                method.invoke(mContext, layoutID);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

2、控制元件註解

還是先建立註解類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

首先通過傳入的Context得到Class物件

Class<?> aClass = mContext.getClass();

通過DeclaredFields方法當前物件得到所有的成員變數

Field[] declaredFields = aClass.getDeclaredFields();

在遍歷獲取這個陣列,通過每一個成員變數的

getAnnotation方法得到註解,如果註解不為空,則說明有id,是需要的。

然後通過getMethod方法反射findViewById得到該方法,然後反射執行,得到View物件,

然後在把當前值給反射物件

 Class<?> aClass = mContext.getClass();
        //獲取到類中所有的成員變數
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //得到成員變數的註解
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            //如果成員變數不為空,說明有註解,則獲取id
            if (viewInject != null) {
                //獲取到控制元件的id
                int valueID = viewInject.value();
                try {
                    //通過反射呼叫findViewById 方法
                    Method method = aClass.getMethod("findViewById", int.class);
                    View view = (View) method.invoke(mContext, valueID);
                    field.setAccessible(true);
                    field.set(mContext, view);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

3、事件註解

實現思路 獲取當前類中所有的方法,然後去遍歷,然後得到方法的註解,然後判斷是不是存在 EventBase,如果有的話就通過反射findViewById方法得到當前控制元件,再得到當前物件的事件,通過動態代理去實現呼叫

首先的拿到事件的名字、型別、和方法名,所以建立了輔助註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
    //對點選事件進行擴充套件

    //設定監聽的方法
    String listenerSetter();

    //事件型別
    Class<?> listenerType();

    //事件被觸發後,執行回撥的方法名稱
    String callBackMethod();
}

然後在建立點選事件的註解(這裡以單擊為例,別的點選方法我也會把程式碼展現的)

單擊事件

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackMethod = "onClick")
public @interface OnClick {
    //那些控制元件的id,進行點選事件設定
    int[] value();
}

實現事件註解,先獲取當前類的所有方法且遍歷,

然後獲取當前方法的註解

Annotation[] annotations = method.getAnnotations();

在判斷當前註解是否有我們設定的三要素,如果沒有直接進入下一迴圈

有的話直接得到三要素

                 String listenerSeter = eventBase.listenerSetter();
                //得到 listenerType--》 View.OnClickListener.class,
                Class<?> listenerType = eventBase.listenerType();
                //callMethod--->onClick
                String callBackMethod = eventBase.callBackMethod();

通過反射得到當前的方法,然後得到他們的控制元件陣列,再去遍歷,然後通過反射findViewById得到控制元件物件

如果物件不為空通過getMethod得到Method物件,然後通過動態代理得到點選事件的物件,然後呼叫。

        Class<?> clazz = mContext.getClass();
        //獲取Activity中所有的方法
        Method[] methods = clazz.getDeclaredMethods();
        //遍歷所有方法
        for (Method method : methods) {
        //            獲取方法上的所有註解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                //獲取註解 anntionType   OnClick  OnLongClck
                Class<?> annotationType = annotation.annotationType();
                //獲取註解的註解   onClick 註解上面的EventBase
                EventBase eventBase = annotationType.getAnnotation(EventBase.class);

                if (eventBase == null) {
                    continue;
                }
                 /*
                開始獲取事件三要素  通過反射注入進去
                1 listenerSetter  返回     setOnClickListener字串
                 */
                String listenerSeter = eventBase.listenerSetter();
                //得到 listenerType--》 View.OnClickListener.class,
                Class<?> listenerType = eventBase.listenerType();
                //callMethod--->onClick
                String callBackMethod = eventBase.callBackMethod();
                //方法名 與方法Method的對應關係
                Map<String, Method> methodMap = new HashMap<>();

                methodMap.put(callBackMethod, method);

                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    int[] viewIDS = (int[]) valueMethod.invoke(annotation);

                    for (int viewID : viewIDS) {
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(mContext, viewID);
                        if (view == null) {
                            continue;
                        }
                         /*
                        listenerSetter  setOnClickLitener
                        listenerType   View.OnClickListener.class
                         */
                        Method setOnClickListener = view.getClass().getMethod(listenerSeter, listenerType);
                        //使用動態代理實現listenerType介面,類似程式碼設定控制元件點選事件
                        ListenerInvocationHandler handler = new ListenerInvocationHandler(mContext, methodMap);
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler);
                        setOnClickListener.invoke(view, proxy);
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

        }

listview的item點選事件

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter ="setOnItemClickListener"
        ,listenerType = AdapterView.OnItemClickListener.class,callBackMethod = "onItemClick")
public @interface OnItemClick {
    int[] value();
}

長按事件

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
        listenerType = View.OnLongClickListener.class,callBackMethod = "onLongClick")

public @interface OnLongClick {
    int[] value() default -1;
}

動態代理方法

public class ListenerInvocationHandler implements InvocationHandler{
    //activity   真實物件
    private Context context;
    private Map<String,Method> methodMap;

    public ListenerInvocationHandler(Context context, Map<String, Method> methodMap) {
        this.context = context;
        this.methodMap = methodMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name=method.getName();
        //決定是否需要進行代理
        Method metf=methodMap.get(name);

        if(metf!=null)
        {
            return  metf.invoke(context,args);
        }else
        {
            return method.invoke(proxy,args);
        }
    }
}

呼叫註解例項

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.test)
    Button buttonTest;

    @ViewInject(R.id.listView)
    ListView listView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
        listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                new String[]{"aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa"}));
    }

    @OnItemClick(R.id.listView)
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        Toast.makeText(MainActivity.this, "aaaaaaaaaaa" + position, Toast.LENGTH_SHORT).show();
    }

    @OnClick(R.id.test)
    public void testClick(View view) {
        Toast.makeText(MainActivity.this, "aaaaaaaaaaa", Toast.LENGTH_SHORT).show();
    }

    @OnLongClick({R.id.test2, R.id.test})
    public boolean testLongClick(View view) {
        Toast.makeText(MainActivity.this, "bbbbbbbbbbbbbbbbbbbbbbbbbb", Toast.LENGTH_SHORT).show();


        return true;
    }
}

我會在我的資源裡儲存原始碼

相關推薦

no