1. 程式人生 > >Github專案解析(六)-->自定義實現ButterKnife框架

Github專案解析(六)-->自定義實現ButterKnife框架

目前在  友友用車  專案中使用到了ButterKnife框架,這是一個通過註解的方式簡化程式設計師程式碼量,自動對映xml佈局檔案與物件關係的框架。使用了這個框架之後很大程度上簡化程式設計師的工作量,提高了工作效率,讓程式設計師們不在編寫findViewById之類的程式碼,其github上的地址  ButterKnife。  最近也研究了一下ButterKnife的實現原理,下面我就將講解一下其實現的機制。
這裡首先簡單介紹一下他的使用方式;Android註解Butterknife的使用及程式碼分析

(一)使用方式

1)在activity中如何使用

@InjectView(R.id.feedback_content_edit)
    EditText feedContent; // 意見反饋功能
@InjectView(R.id.feedback_contact_edit) EditText feedContact; // 聯絡方式 @InjectView(R.id.b3_button) Button feedOk; // 提交按鈕 @OnClick(R.id.open_car_door) public void openCarDorClick() { dosomething; } @Override protected void onCreate(Bundle savedInstanceState) { super
.onCreate(savedInstanceState); setContentView(R.layout.activity_feedback); ButterKnife.inject(this); initView(); }

是不是很簡單?其實這就是butterKnife的基本用法了

  • 通過註解(@InjectView)的方式將xml佈局檔案中定義的元件與Activity中定義的元件物件對應起來
  • 通過註解(@OnClick)實現對佈局檔案的點選事件

  • 在onCreate方法中通過靜態方法:ButterKnife.inject(this);真正的將xml佈局元件與activity中的元件物件對映起來;

2)在Fragment中如何使用

public class SimpleFragment extends Fragment {

    @InjectView(R.id.fragment_text_view)
    TextView mTextView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_simple, container, false);
        ButterKnife.inject(this, view);
        mTextView.setText("TextView in Fragment are found!");
        return view;
    }
}

注意這裡需要呼叫ButterKnife的過載方法:

public static void inject(Object target, View source) {
        inject(target, source, ButterKnife.Finder.VIEW);
    }

3)在Adapter的ViewHolder是如何使用的

static class ViewHolder {
        @InjectView(R.id.person_name)
        TextView name;
        @InjectView(R.id.person_age)
        TextView age;
        @InjectView(R.id.person_location)
        TextView location;
        @InjectView(R.id.person_work)
        TextView work;

        public ViewHolder(View view) {
            ButterKnife.inject(this, view);
        }
    }

可以發現ButterKnife的主要使用作用是簡化我們載入xml佈局檔案的寫法,通過註解的方式將記憶體物件與佈局檔案繫結,這對於懶程式設計師真是一個偷懶的利器啊。

那麼ButterKnife的實現原理是怎樣的呢?我們能否自己實現一個簡單的ButterKnife框架呢?帶著這兩個問題,我們開始今天的自定義ButterKnife之旅。

預備知識點:

  • java註解相關

下面是一段關於java中註解的說明:

註解相當於一種標記,在程式中加了註解就等於為程式打上了某種標記,沒加,則等於沒有某種標記,以後,javac編譯器,開發工具和其他程式可以用反射來了解你的類及各種元素上有無何種標記,看你有什麼標記,就去幹相應的事。標記可以加在包,類,欄位,方法,方法的引數以及區域性變數上。

  • java反射相關

下面一段是百度百科中關於java反射的說明:

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

具體關於java反射方面的內容我們可以參考:JAVA中的反射機制

在瞭解了java的註解和反射機制之後我們可以開始我們的自定義實現ButterKnife之旅了。

(二)繫結View元件;

  1. 自定義註解
@Target(ElementType.FIELD) // 標識改註解應用在成員變數上
@Retention(RetentionPolicy.RUNTIME) // 標識生命週期是執行時
public @interface ViewBinder {
    int id() default -1;
}

@interface是用於自定義註解的,它裡面定義的方法的宣告不能有引數,也不能丟擲異常,並且方法的返回值被限制為簡單型別、String、Class、emnus、@interface,和這些型別的陣列。
關於註解方面的內容,可自行查詢學習;

  1. 建立View解析類
/**
 * View解析類,主要用於解析含有註解的View成員變數
 */
public class ViewBinderParser {

    /**
     * 初始化解析
     * @param object
     */
    public static void inject(Object object) {
        // 建立解析物件並開始執行解析方法
        ViewBinderParser parser = new ViewBinderParser();
        try {
            parser.parser(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 開始執行解析方法
     * @param object
     * @throws Exception
     */
    public void parser(final Object object) throws Exception{
        View view = null;
        // 獲取目標物件位元組碼
        final Class<?> clazz = object.getClass();
        // 獲取目標物件定義的成員變數
        Field[] fields = clazz.getDeclaredFields();
        // 迴圈遍歷成員變數
        for (Field field : fields) {
            if (field.isAnnotationPresent(ViewBinder.class)) {
                ViewBinder inject = field.getAnnotation(ViewBinder.class);
                int id = inject.id();
                if (id < 0) {
                    throw new Exception("id must not be null!!!");
                }
                if (id > 0) {
                    field.setAccessible(true);
                    if (object instanceof View) {
                        view = ((View) object).findViewById(id);
                    } else if (object instanceof Activity) {
                        view = ((Activity) object).findViewById(id);
                    }
                    field.set(object, view);// 給我們要找的欄位設定id
                }
            }
        }
    }
}

這裡可以看出,主要是通過建立一個解析物件,然後通過反射方法獲取定義的成員變數,判斷是否註解屬性,如果是的話,則將註解的id值通過findViewById獲取並給View物件賦值;

  1. 在activity執行解析方法
/**
 * 測試Activity,主要用於測試通過註冊實現View元件的初始化過程
 */ 
public class MainActivity extends AppCompatActivity {

    @ViewBinder(id = R.id.button1)
    public Button button1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ViewBinderParser.inject(MainActivity.this);

                Log.i("tag", button1.getText() + " ####");
            }
        });

    }

}

可以發現當我們點選Button2的時候我們執行了Log.i方法,並將button1的text打印出來了,正式我們在佈局檔案中初始化的時候設定的text字串,從而說明我們通過註解的方式實現了button1元件的初始化工作,初始化過程可能有一些地方有待優化,但這個其實就是butterKnife框架實現元件初始化工作的核心流程。

既然我們已經實現了元件的初始化工作,下面我們將嘗試的繫結View元件的事件點選事件。

(三)繫結View的OnClick事件

(一)自定義OnClick註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int id() default -1;
}

和設定View元件的初始化註解類似,這裡定義了元件點選事件OnClick的註解。

(二)建立解析類,解析方法

/**
     * 解析成員方法
     * @param clazz
     * @throws Exception
     */
    public void parserMethod(Class<?> clazz, final Object object) throws Exception{
        View view = null;
        // 獲取目標物件定義的成員方法
        Method[] methods = clazz.getDeclaredMethods();
        // 迴圈遍歷成員變數
        for (final Method method : methods) {
            if (method.isAnnotationPresent(OnClick.class)) {
                OnClick inject = method.getAnnotation(OnClick.class);
                int id = inject.id();
                if (id < 0) {
                    throw new Exception("id must not be null!!!");
                }
                if (id > 0) {
                    if (object instanceof View) {
                        view = ((View) object).findViewById(id);
                    } else if (object instanceof Activity) {
                        view = ((Activity) object).findViewById(id);
                    }
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(object, null);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
    }

(三)在activity中測試解析

/**
 * 測試Activity,主要用於測試元件的點選事件
 */ 
public class MainActivity extends AppCompatActivity {

    @ViewBinder(id = R.id.button1)
    public Button button1;

    /**
     * 執行button1的點選事件
     */
    @OnClick(id = R.id.button1)
    public void button1OnClick() {
        Log.i("tag", "這是一個測試的例子");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBinderParser.inject(MainActivity.this);
    }

}

可以發現我們在Activity中為button1OnClick方法綁定了元件id,這樣就實現了button1元件的事件繫結定義,然後我們在Activity的onCreate方法執行了ViewBinderParser.inject方法,這樣就真正實現了對元件的事件繫結,當我們點選button1的時候執行了Log.i方法,並列印:”這是一個測試的例子”,說明我們元件繫結操作執行成功了。O(∩_∩)O哈哈~

總結:

  • 本文主要分析了一下ButterKnife的簡單實用與實現原理,並實際實現了一個簡單的ButterKnife框架。

  • 已經將自己實現的ButterKnife框架上傳至  我的github中  感興趣的同學可以star和follow。

  • 自己實現簡單的ButterKnife框架主要涉及到了java中的註解和反射相關知識。