Java基礎(chǔ)回歸之注解Annotation【低仿ButterKnife實(shí)戰(zhàn)篇】

前言

書接上回蚁趁,上回說到庫里對戰(zhàn)湖人三分10投0中鞠眉,真真氣煞我?guī)煲残拦拢@下把氣全撒在鵜鶘身上,一口氣轟下破紀(jì)錄的13記三分呀潭。 上回說到Java基礎(chǔ)回歸之注解Annotation【基礎(chǔ)篇】钉迷,這回我們來真刀真槍實(shí)戰(zhàn)。相信很多做安卓的同學(xué)都用過至少聽過ButterKnife钠署,沒錯(cuò)糠聪,就是大神JakeWharton的黃油刀。本篇博文將結(jié)合注解和反射谐鼎,實(shí)現(xiàn)類似Jake Wharton大神的ButterKnife注入框架舰蟆,需要說明的是ButterKnife實(shí)現(xiàn)方式和我們的實(shí)現(xiàn)方式是有區(qū)別的,它里面是用到了APT技術(shù)狸棍,他是基于編譯期的注入身害,所以效率比我們用反射高,本文是基于運(yùn)行期的草戈。但是這并不妨礙我們造輪子塌鸯。
ps:對反射不熟悉可以看博主另外一篇博文:Java基礎(chǔ)回歸之反射Reflection,本文不講解ButterKnife的用法唐片。

取名

既然是低仿大名鼎鼎ButterKnife(黃油刀)丙猬,那我們項(xiàng)目的名字也要低仿涨颜,就叫Shaver(剃須刀)吧..

Shaver的功能點(diǎn)

  • @Bind(R.id.btn1):用于注入view,代替繁瑣的findViewById操作
  • @ContentView(R.layout.activity_main):用于代替注入contentView
  • @StringRes(R.string.string_shaver):用于注入String資源文件
  • @OnClick({R.id.btn1,R.id.btn2}):用于綁定view的監(jiān)聽事件

目標(biāo)效果

目標(biāo)效果

開始造輪子

我先幫大家捋一遍思路茧球,其實(shí)說白了就是編寫上述4個(gè)注解類庭瑰,然后編寫一個(gè)處理這四種注解的核心類。例如要處理綁定view的@Bind注解袜腥,我們需要將activity傳入到核心類中见擦,核心類反射獲取到標(biāo)注有@Bind注解的成員變量field,然后獲取該注解的value羹令,即view的id,最后將id利用activity.findViewById(value)獲取到view损痰,然后反射將獲取到的view賦值給改成員變量filed福侈,這樣我們就成功將view注入進(jìn)去了,其他三個(gè)注解同理卢未,下面show you the code肪凛,代碼注釋很詳細(xì),請仔細(xì)看辽社。

  • 首先理所應(yīng)當(dāng)伟墙,我們編寫4個(gè)注解類:
  • @Bind注解類
package com.youzhi.shaver.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 代替findViewById的注解 */
@Target(ElementType.FIELD)//標(biāo)注目標(biāo)為成員變量
@Retention(RetentionPolicy.RUNTIME)//生命周期為運(yùn)行時(shí)
public @interface Bind {  
  int value();//用于保存控件id
}
  • @ContentView注解類
package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 代替setContentView的注解 */
@Target(ElementType.TYPE)//標(biāo)注目標(biāo)為類前
@Retention(RetentionPolicy.RUNTIME)//生命周期為運(yùn)行時(shí)
public @interface ContentView {
    int value();//用于保存layoutId
}
  • @StringRes注解類
package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 代替getResource().getString(R.string.xx) */
@Retention(RetentionPolicy.RUNTIME)//生命周期為運(yùn)行時(shí)
@Target(ElementType.FIELD)//標(biāo)注目標(biāo)為成員變量
public @interface StringRes {
    int value();//用于保存string的id
}
  • OnClick注解類
package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 代替setOnClickListener的注解 */
@Retention(RetentionPolicy.RUNTIME)//生命周期為運(yùn)行時(shí)
@Target(ElementType.METHOD)//標(biāo)注目標(biāo)為方法上
public @interface OnClick {
    int[] value();//用于保存控件id集
}
  • 接下來是最核心的處理類,所有邏輯都在這個(gè)處理類上面滴铅。(ps:可以優(yōu)化戳葵,例如用Map將view緩存起來...這里留給大家)
package com.youzhi.shaver.core;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/** * 注入接口 */
public class Shaver {
    public static void bind(Activity activity) {
        try {
            bindContentView(activity);
            bindStringRes(activity);
            bindViews(activity);
            bindClicks(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 注入contentView
     * @param activity
     */
    private static void bindContentView(Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        ContentView annotation = aClass.getAnnotation(ContentView.class);
        if(null != annotation){
            int layoutId = annotation.value();//獲得注解上的layoutId值
            activity.setContentView(layoutId);//將layoutId設(shè)置給activity
            //activity.setContentView(layoutId)也可用反射實(shí)現(xiàn),但效率低且麻煩汉匙,所以直接使用上面setContentView(layoutId)方法拱烁,此處只是順便說一下反射調(diào)方法 
           //aClass.getMethod("setContentView",int.class).invoke(activity,layoutId); 
       }
    }
    /**
     * 注入String資源
     * @param activity
     */
    private static void bindStringRes(Activity activity) throws IllegalAccessException {
        Class<? extends Activity> aClass = activity.getClass();
        Field[] fields = aClass.getDeclaredFields();
        //遍歷成員變量取出被StringRes注解的field
        for (Field field : fields) {
            if(field.isAnnotationPresent(StringRes.class)){
                StringRes annotation = field.getAnnotation(StringRes.class);
                int stringId = annotation.value();//string資源文件id
                String stringValue = activity.getString(stringId);//獲取到相應(yīng)資源文件string值
                //反射賦值
                field.setAccessible(true);//破封裝
                field.set(activity,stringValue);
            }
        }
    }
    /**
     * 注入view
     * @param activity
     * @throws IllegalAccessException
     */
    private static void bindViews(Activity activity) throws IllegalAccessException/*, NoSuchMethodException, InvocationTargetException */{
        //反射拿到@Bind注解的成員變量
        Class<? extends Activity> aClass = activity.getClass();
        Field[] fields = aClass.getDeclaredFields();//拿到所有成員變量
        for (Field field : fields) {
            //遍歷成員變量,判斷成員變量上是否有@Bind注解
            if (field.isAnnotationPresent(Bind.class)) {
                //如果有噩翠,拿出注解的value值戏自,即控件id
                Bind bind = field.getAnnotation(Bind.class);
                int viewId = bind.value();
                View view = activity.findViewById(viewId);//獲取到view對象
                //activity.findViewById(viewId)也可用反射實(shí)現(xiàn),但效率低且麻煩伤锚,所以直接使用上面find方法擅笔,此處只是順便說一下反射調(diào)方法
                //View view= (View) aClass.getMethod("findViewById",int.class).invoke(activity,viewId);
                field.setAccessible(true);//破封裝
                field.set(activity, view);//將view設(shè)置給該成員變量
            }
        }
    }

    /**
     * 綁定監(jiān)聽事件
     * @param activity
     */
    private static void bindClicks(final Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        Method[] declaredMethods = aClass.getDeclaredMethods();//反射獲取方法
        //遍歷方法,判斷方法上是否有@OnClick注解
        for (final Method method : declaredMethods) {
            if(method.isAnnotationPresent(OnClick.class)){
                OnClick annotation = method.getAnnotation(OnClick.class);
                int[] viewIds = annotation.value();//拿到該方法上注解的view的id集
                for (int viewId : viewIds) {
                    final View view = activity.findViewById(viewId);
                    if(null != view){
                        view.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                try {
                                    method.setAccessible(true);//破封裝
                                    method.invoke(activity,view);//調(diào)起該帶有@OnClick注解方法
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                }
            }
        }
    }
}

我們看到屯援,Shaver類里面定義了一個(gè)入口方法bind(Activity activity) 猛们,然后bind方法里面我們調(diào)用了4個(gè)方法,這4個(gè)方法分別是處理4個(gè)注解邏輯玄呛,其思路上面已經(jīng)說了阅懦,就是通過反射掃描帶有這4個(gè)注解的編程元素(類/方法/成員變量),然后獲取注解上的value徘铝,拿到這些id后耳胎,我們就可以做很多事了惯吕。代碼本身不復(fù)雜,這里就不多說了怕午。

  • Shaver使用
package com.youzhi.shaver;
//省略各種導(dǎo)包
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @Bind(R.id.btn1)
    Button btn1;
    @Bind(R.id.btn2)
    Button btn2;
    @Bind(R.id.tv1)
    TextView tv1;
    @Bind(R.id.et1)
    EditText et1;
    @StringRes(R.string.string_shaver)
    String stringRes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Shaver.bind(this);
        Log.e("MainActivity", btn1.getText().toString() + " , " + btn2.getText().toString() + " , " + tv1.getText().toString() + " , " + et1.getText().toString());
        tv1.setText(stringRes);
    }
    @OnClick({R.id.btn1,R.id.btn2})
    public void onClicks(View view){
        switch (view.getId()){
            case R.id.btn1:
                Toast.makeText(this, "點(diǎn)擊了btn1", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn2:
                Toast.makeText(this, "點(diǎn)擊了btn2", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

然后我們在String文件中有一個(gè)string_shaver废登,用于例子中注入@StringRes(R.string.string_shaver)

String資源文件.png

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/btn1"
        android:layout_width="150dp"
        android:background="#33ff00ff"
        android:layout_marginBottom="10dp"
        android:text="按鈕1"
        android:layout_height="30dp"/>
    <Button
        android:id="@+id/btn2"
        android:text="按鈕2"
        android:layout_width="150dp"
        android:background="#33ffff00"
        android:layout_marginBottom="10dp"
        android:layout_height="30dp"/>
    <TextView
        android:id="@+id/tv1"
        android:layout_width="150dp"
        android:background="#4433bb00"
        android:layout_marginBottom="10dp"
        android:gravity="center"
        android:text="文本1"
        android:layout_height="30dp"/>
    <EditText
        android:id="@+id/et1"
        android:layout_width="150dp"
        android:background="#443300ff"
        android:layout_marginBottom="10dp"
        android:gravity="center"
        android:text="輸入文本框1"
        android:layout_height="30dp"/>

</LinearLayout>
  • 運(yùn)行結(jié)果及打印的log
運(yùn)行結(jié)果.png

log.png

我們成功獲取到各個(gè)view的文本,以及設(shè)置上點(diǎn)擊事件郁惜,說明我們的Shaver起作用了堡距!It works!

這里需要注意打印出來的文本框?yàn)槭裁催\(yùn)行結(jié)果是“我是字符串資源”而log是“文本1”兆蕉?大家看仔細(xì)點(diǎn)羽戒,我們布局文件里面text是“文本1”,然后我們在MainActivity里面打印出來后虎韵,我們重新set了一次易稠,改成string文件里的值了。

The End

我們的低仿ButterKnife到此結(jié)束包蓝,相信講解的已經(jīng)夠仔細(xì)了驶社,到這里我相信現(xiàn)在對反射及注解有更深入的理解和鞏固。
最后测萎,轉(zhuǎn)載請注明出處亡电。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市硅瞧,隨后出現(xiàn)的幾起案子份乒,更是在濱河造成了極大的恐慌,老刑警劉巖零酪,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冒嫡,死亡現(xiàn)場離奇詭異,居然都是意外死亡四苇,警方通過查閱死者的電腦和手機(jī)孝凌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來月腋,“玉大人蟀架,你說我怎么就攤上這事∮苌В” “怎么了片拍?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妓肢。 經(jīng)常有香客問我捌省,道長,這世上最難降的妖魔是什么碉钠? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任纲缓,我火速辦了婚禮卷拘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祝高。我一直安慰自己栗弟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布工闺。 她就那樣靜靜地躺著乍赫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陆蟆。 梳的紋絲不亂的頭發(fā)上雷厂,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音遍搞,去河邊找鬼罗侯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溪猿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纫塌,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼诊县,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了措左?” 一聲冷哼從身側(cè)響起依痊,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怎披,沒想到半個(gè)月后胸嘁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凉逛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年性宏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片状飞。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毫胜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诬辈,到底是詐尸還是另有隱情酵使,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布焙糟,位于F島的核電站口渔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏穿撮。R本人自食惡果不足惜缺脉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一痪欲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枪向,春花似錦勤揩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至深员,卻和暖如春负蠕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倦畅。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工遮糖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叠赐。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓欲账,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芭概。 傳聞我的和親對象是個(gè)殘疾皇子赛不,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容