Android開發學習之路--Annotation注解簡化view控件之初體驗


    一般我們在寫android Activity的時候總是會在onCreate方法中加上setContentView方法來加載layout,通過findViewById來實現控件的綁定,每次寫這么多代碼總覺得很煩躁。近來看了下android中有Annotation來實現這方面的簡化,對於java不是很了解,就簡單的看了下。上次玩web的時候,springmvc也有很多的注解,不知道怎么實現的,這里其實基本上類似。

    Annotation注解這里主要還是講講怎么使用吧,單純的原理會把人繞進去的,沒辦法,java基礎只能后面再補了,c搞久了,很多面向對象的思想只停留在大學的時候,除了linux內核的一些面向對象的思想。說了那么多的廢話,接着繼續我們的Annotation的學習吧,先新建工程emAnnotationStudy,新建EMLayoutBinder.java,代碼如下:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by jared on 16/3/10.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EMLayoutBinder {
int value();
}

    這里的@Target,@Retention和@interface,先來簡單的介紹下這幾個內容吧。

    @Target:說明了Annotation修飾的對象范圍,Annotation可被用於packages、types等類,接口,枚舉,Annotation類型;還可以是類成員方法,構造方法,成員變量,枚舉值;方法參數和本地變量等。其一般有如下幾種類型:

    ElementType.CONSTRUCTOR:  構造器聲明;

    ElementType.FIELD:                    成員變量、對象、屬性;

    ElementType.LOCAL_VARIABLE:  局部變量聲明;

    ElementType.METHOD:                 方法聲明;

    ElementType.PACKAGE:                包聲明;

    ElementType.PARAMETER:           參數聲明;

    ElementType.TYPE:                        類、接口(包括注解類型)或enum聲明;

    這用到了TYPE。

     @Retention:表示在什么級別保存該注解信息。其一般級別如下:

    RetentionPolicy.SOURCE:    停留在java源文件,編譯器被丟掉。

    RetentionPolicy.CLASS:     停留在class文件中,但會被VM丟棄。

    RetentionPolicy.RUNTIME:內存中的字節碼,VM將在運行時也保留注解,因此可以通過反射機制讀取注解的信息。

    這里給了最高級別RUNTIME。

    @interface:這個就表示注解了,和interface很像,不過多了一個@符號。

    int value():表示傳入的參數是int類型的。

    好了,既然定義好了那么怎么使用呢?單單一個注解怎么個搞搞?其實注解一般都是和java的反射原理一起使用的。還是簡單學習下java的反射吧,網上資料很多,這里就簡單理解理解了。在java中的反射機制,被稱為Reflection,它允許運行中的java程序對自身進行檢查,並能直接操作程序的內部屬性或方法。

    利用Reflection APIs可以獲取任何已知名稱的類的內部信息,包括package、type parameters、superclass、implemented interfaces、inner classes、outer classes、fields constructors、methods、modifiers等。

    Class:          表示某個具體的類或接口

    Object:        每個類都使用Object 做為超類,所有對象都實現這個類的方法

    Constructor:封裝了Class的構造方法

    Field:           提供有關類或接口的屬性信息,以及對它的動態訪問權限

    Method:      提供類或者接口上的方法的信息

    Modifier:     封裝了Class(method、fields)的修飾域。

    簡單了解下java的發射機制,其實反射就是通過反向調用類的一些功能,可能會覺得很難理解,還是繼續我們的學習吧,新建EMAnnotationParser類:

package com.jared.emannotationstudy;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;

/**
* Created by jared on 16/3/10.
*/
public class EMAnnotationParser {
public static void injectActivity(Activity activity) {
if (null == activity) {
return;
}
Class<Activity> activityClass = (Class<Activity>) activity.getClass();
if (isEMLayoutBinder(activityClass)) {
EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class);
activity.setContentView(layout.value());
}
View decorView = activity.getWindow().getDecorView();
}

private static boolean isEMLayoutBinder(Class<?> c) {
return c.isAnnotationPresent(EMLayoutBinder.class);
}


    這里實現了injectActivity的方法,通過getClass獲取當前的Activity的class,然后通過isAnnotationPresent查看該Annotation,再通過getAnnotation獲取該注解,接着就是把注解傳入的那個layout通過activity的setContentView方法來加載到activity 中了。好了,那么我們來實現下MainActivity吧:

package com.jared.emannotationstudy;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

@EMLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EMAnnotationParser.injectActivity(this);
//setContentView(R.layout.activity_main);
}
}

    去掉了setContentView,直接一個@EMLayout就搞定了,是不是很方便,這里通過EMAnnotationParser的injectActivity方法。其實一般項目中會定義一個BaseActivity,MainActivity通過繼承BaseActivity來實現,那樣看上去會更加的清晰,那就實現下BaseActivity吧:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

/**
* Created by jared on 16/3/10.
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EMAnnotationParser.injectActivity(this);
}
}

    然后實現MainActivity繼承BaseActivity:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

@EMViewBinder(R.id.hello)
private TextView mHello;
@EMViewBinder(R.id.test1)
private Button mTest1;
@EMViewBinder(R.id.test2)
private Button mTest2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHello.setText("Hello Annotation!");
}
}


    運行后依然沒有任何問題。既然layout通過注解了,那么控件也是可以通過注解的,接下去就去實現下了。首先新建Annotation為EMViewBinder:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by jared on 16/3/10.
*/

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

    這里就很好理解了,和LayoutBinder一樣,只是target是FIFLD,因為是成員的變量。接着簡單實現下反射:

package com.jared.emannotationstudy;

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;

/**
* Created by jared on 16/3/10.
*/
public class EMAnnotationParser {
public static void injectActivity(Activity activity) {
if (null == activity) {
return;
}
Class<Activity> activityClass = (Class<Activity>) activity.getClass();
if (isEMLayoutBinder(activityClass)) {
EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class);
activity.setContentView(layout.value());
}
View decorView = activity.getWindow().getDecorView();
initViews(activityClass.getDeclaredFields(), decorView, activity);
}

private static boolean isEMLayoutBinder(Class<?> c) {
return c.isAnnotationPresent(EMLayoutBinder.class);
}

private static boolean isEMViewBinder(Field filed) {
return filed.isAnnotationPresent(EMViewBinder.class);
}

private static void initViews(Field[] fields, View view, Object object) {
View view1;
for (Field field : fields) {
if(isEMViewBinder(field)) {
EMViewBinder emView = field.getAnnotation(EMViewBinder.class);
view1 = view.findViewById(emView.value());
if(null != view1) {
try {
field.setAccessible(true);
field.set(object, view1);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
}
}
}

    這里通過getDecorView來獲取view,通過getDeclaredFields來獲取Fields,然后通過getAnnotation來獲取EMViewBinder注解,接着調用findViewById來找到這個控件。如果找到了,那么需要調用setAccessible為true,因為變量一般都是private的。大概的意思就這樣了,下面我們修改下layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
tools:context="com.jared.emannotationstudy.MainActivity">

<TextView
android:id="@+id/hello"
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_gravity="center"/>

<Button
android:id="@+id/test1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test1"
android:textAllCaps="false"/>

<Button
android:id="@+id/test2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test2"
android:textAllCaps="false"/>
</LinearLayout>

    接着我們在MainActivity中添加代碼如下:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

@EMViewBinder(R.id.hello)
private TextView mHello;
@EMViewBinder(R.id.test1)
private Button mTest1;
@EMViewBinder(R.id.test2)
private Button mTest2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHello.setText("Hello Annotation!");
}
}

    運行看下效果如下:


    完全達到了我們的預期,而且編寫代碼十分方便,不需要再引入一大堆的findViewById了。即使再添加更多的控件也輕松搞定。既然控件綁定好了,那么接下去還需要做的就是事件的綁定了。這里主要實現button的事件,新建EMOnClickBinder:

package com.jared.emannotationstudy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by jared on 16/3/10.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EMOnClickBinder {
int[] value();
}

    這里實現的是方法,所以使用了METHOD,而且button可能很多,所以用了int[] 數組。接着我們實現具體的反射:

private static boolean isEMOnClickBinder(Method method) {
return method.isAnnotationPresent(EMOnClickBinder.class);
}

private static void initOnClick(Method[] allMethod, View root, Object object) {
for (Method method : allMethod) {
if (isEMOnClickBinder(method)) {
EMOnClickBinder onClick = method.getAnnotation(EMOnClickBinder.class);
MyOnClickListener click = new MyOnClickListener(method, object);
int[] ids = onClick.value();
for (int id : ids) {
root.findViewById(id).setOnClickListener(click);
}
}
}
}

static class MyOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mReceiver;

public MyOnClickListener(Method method, Object receiver) {
mMethod = method;
mReceiver = receiver;
}

@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);
mMethod.invoke(mReceiver, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

    這里使用ID查找,調用setOnClickListener方法來注冊方法,通過MyOnClickListener來實現具體的操作。當有事件觸發的時候會調用onClick方法,進而調用method的invoke方法。就會調用到注解下的自定義方法了,這里傳入的就是View。接着具體MainActivity的實現如下:

package com.jared.emannotationstudy;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

@EMLayoutBinder(R.layout.activity_main)
public class MainActivity extends BaseActivity {

@EMViewBinder(R.id.hello)
private TextView mHello;
@EMViewBinder(R.id.test1)
private Button mTest1;
@EMViewBinder(R.id.test2)
private Button mTest2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHello.setText("Hello Annotation!");
}

@EMOnClickBinder({R.id.test1, R.id.test2})
public void myOnClick(View view) {
switch (view.getId()) {
case R.id.test1:
mHello.setText("I am test1");
break;
case R.id.test2:
mHello.setText("I am test2");
default:
break;
}
}
}

    是不是非常的簡單清晰,以后把這幾個文件當作工具,簡單封裝下,就可以不用每次寫那么多的findViewById和setOnClickListener了。基本上Annotation就先學習到這里了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com