Butterknife全方位解析

zvci3409 8年前發布 | 8K 次閱讀 Android開發 移動開發 butterknife

概述

Butterknife是供職于Square公司的JakeWharton大神開發的開源庫,使用這個庫,在AS中搭配 Android ButterKnife Zelezny插件,可以大大提高開發的效率,從此擺脫繁瑣的findViewById(int id),也不用自己手動@bind(int id) , 直接用插件生成即可。本篇文章將對Butterknife進行深入解析。

ButterKnife有以下優點:

1、強大的View綁定和Click事件處理功能,簡化代碼,提升開發效率

2、方便的處理Adapter里的ViewHolder綁定問題

3、運行時不會影響APP效率,使用配置方便

4、代碼清晰,可讀性強

如何導入ButterKnife

在項目的build.grade文件中進行如下配置:

buildscript {
    repositories {
        jcenter()
        mavenCentral()
        maven {
            url "

}

}</code></pre>

例如:

buildscript {
    repositories {
        jcenter()
        mavenCentral()
        maven {
            url "

}

dependencies {
    classpath 'com.android.tools.build:gradle:2.2.2'
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

}

allprojects { repositories { jcenter() } }

task clean(type: Delete) { delete rootProject.buildDir }</code></pre>

在app的build.grade文件中進行如下配置:

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android{...}

dependencies { //視圖綁定 butterknife compile 'com.jakewharton:butterknife:8.4.0' apt 'com.jakewharton:butterknife-compiler:8.4.0' }</code></pre>

例如:

apply plugin: 'com.android.application'
apply plugin: 'android-apt'

android { compileSdkVersion 24 buildToolsVersion "24.0.3"

defaultConfig {

    minSdkVersion 14
    targetSdkVersion 24
    versionCode 1
    versionName "1.0"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

}

dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])

compile 'com.jakewharton:butterknife:8.4.0'
apt 'com.jakewharton:butterknife-compiler:8.4.0'

}</code></pre>

如何使用ButterKnife

1) 由于每次都要在Activity中的onCreate綁定Activity,所以個人建議寫一個BaseActivity完成綁定,子類繼承即可

注:ButterKnife.bind(this);綁定Activity 必須在setContentView之后:

實現如下(FragmentActivity 實現一樣):

public abstract class BaseActivity extends Activity {
public abstract int getContentViewId();

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(getContentViewId());  
    ButterKnife.bind(this);  
    initAllMembersView(savedInstanceState);  
}  

protected abstract void initAllMembersView(Bundle savedInstanceState);  

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    ButterKnife.unbind(this);//解除綁定,官方文檔只對fragment做了解綁  
}  

}</code></pre>

2) 綁定fragment

public abstract class BaseFragment extends Fragment {
public abstract int getContentViewId();
protected Context context;
protected View mRootView;

@Nullable  
@Override  
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {  
    mRootView =inflater.inflate(getContentViewId(),container,false);  
    ButterKnife.bind(this,mRootView);//綁定framgent  
    this.context = getActivity();  
    initAllMembersView(savedInstanceState);  
    return mRootView;  
}  

protected abstract void initAllMembersView(Bundle savedInstanceState);  

@Override  
public void onDestroyView() {  
    super.onDestroyView();  
    ButterKnife.unbind(this);//解綁  
}  

}</code></pre>

3) 控件id 注解: @BindView()

package com.myl.test;

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

import butterknife.BindView; import butterknife.ButterKnife;

public class ButterknifeActivity extends AppCompatActivity {

@BindView( R.id.button1 )
public Button button1 ;

// 注意:button 的修飾類型不能是:private 或者 static 。 否則會報錯:錯誤: @BindView fields must not be private or static. (com.myl.test.ButterknifeActivity.button1)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_butterknife);
    //綁定activity
    ButterKnife.bind( this ) ;

    button1.setText( "I am a button ");
}

}</code></pre>

4) 多個控件id 注解: @BindViews()

package com.myl.test;

import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Button; import java.util.List; import butterknife.BindViews; import butterknife.ButterKnife;

public class Main2Activity extends AppCompatActivity {

@BindViews({ R.id.button1  , R.id.button2 ,  R.id.button3 })
public List<Button> buttonList ;

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

    ButterKnife.bind(this);

    buttonList.get( 0 ).setText( "hello 1 ");
    buttonList.get( 1 ).setText( "hello 2 ");
    buttonList.get( 2 ).setText( "hello 3 ");
}

}</code></pre>

5) @BindString() :綁定string 字符串

package com.myl.test;

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

import butterknife.BindString; import butterknife.BindView; import butterknife.ButterKnife;

public class ButterknifeActivity extends AppCompatActivity {

@BindView( R.id.button1 ) //綁定button 控件
public Button button1 ;

@BindString( R.string.app_name )  //綁定string 字符串
String meg;

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

    //綁定activity
    ButterKnife.bind( this ) ;

    button1.setText( meg );
}

}</code></pre>

6) @BindArray() : 綁定string里面array數組

<resources>
    <string name="app_name">校園助手</string>

<string-array name="city">
    <item>東莞市</item>
    <item>廣州市</item>
    <item>珠海市</item>
    <item>肇慶市</item>
    <item>深圳市</item>
</string-array>

</resources>

package com.myl.test;

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

import butterknife.BindArray; import butterknife.BindView; import butterknife.ButterKnife;

public class ButterknifeActivity extends AppCompatActivity {

@BindView( R.id.button1 ) //綁定button 控件
public Button button1 ;

@BindArray(R.array.city )  //綁定string里面array數組
String [] citys ;

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

    //綁定activity
    ButterKnife.bind( this ) ;

    button1.setText( citys[0] );
}

}</code></pre>

7) @BindBitmap( ) : 綁定Bitmap 資源

package com.myl.test;

import android.graphics.Bitmap; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ImageView;

import butterknife.BindBitmap; import butterknife.BindView; import butterknife.ButterKnife;

public class ButterknifeActivity extends AppCompatActivity {

@BindView( R.id.imageView ) //綁定ImageView 控件
public ImageView imageView ;

@BindBitmap( R.mipmap.wifi )  //綁定Bitmap 資源
public Bitmap wifi_bitmap ;

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

    //綁定activity
    ButterKnife.bind( this ) ;

    imageView.setImageBitmap( wifi_bitmap );
}

}</code></pre>

8) @BindColor( ) : 綁定一個顏色值

package com.myl.test;

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

import butterknife.BindColor; import butterknife.BindView; import butterknife.ButterKnife;

public class ButterknifeActivity extends AppCompatActivity {

@BindView( R.id.button1 )  //綁定一個控件
public Button button1 ;

@BindColor( R.color.colorAccent ) int black ;  //綁定一個顏色值

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

    //綁定activity
    ButterKnife.bind( this ) ;

    button1.setTextColor(  black );

}

}</code></pre>

9) Adapter ViewHolder 綁定

public class TestAdapter extends BaseAdapter {
private List<String> list;
private Context context;

public TestAdapter(Context context, List<String> list) {  
    this.list = list;  
    this.context = context;  
}  

@Override  
public int getCount() {  
    return list==null ? 0 : list.size();  
}  

@Override  
public Object getItem(int position) {  
    return list.get(position);  
}  

@Override  
public long getItemId(int position) {  
    return position;  
}  

@Override  
public View getView(int position, View convertView, ViewGroup parent) {  
    ViewHolder holder;  
    if (convertView == null) {  
        convertView = LayoutInflater.from(context).inflate(R.layout.layout_list_item, null);  
        holder = new ViewHolder(convertView);  
        convertView.setTag(holder);  
    } else {  
        holder = (ViewHolder) convertView.getTag();  
    }  
    holder.textview.setText("item=====" + position);  
    return convertView;  
}  

static class ViewHolder {  
    @Bind(R.id.hello_world)  
    TextView textview;  

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

}</code></pre>

10) 點擊事件的綁定:不用聲明view,不用setOnClickLisener()就可以綁定點擊事件

a. 直接綁定一個方法

@OnClick(R.id.submit)  
public void submit(View view) {  
  // TODO submit data to server...  
}

b. 所有監聽方法的參數是可選的

@OnClick(R.id.submit)  
public void submit() {  
  // TODO submit data to server...  
}

c. 定義一個特定類型,它將自動被轉換

@OnClick(R.id.submit)  
public void sayHi(Button button) {  
  button.setText("Hello!");  
}

d. 多個view統一處理同一個點擊事件,很方便,避免抽方法重復調用的麻煩

@OnClick({ R.id.door1, R.id.door2, R.id.door3 })  
public void pickDoor(DoorView door) {  
  if (door.hasPrizeBehind()) {  
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();  
  } else {  
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();  
  }  
}

e. 自定義view可以綁定自己的監聽,不指定id

public class FancyButton extends Button {  
  @OnClick  
  public void onClick() {  
    // TODO do something!  
  }  
}

f. 給EditText加addTextChangedListener(即添加多回調方法的監聽的使用方法),利用指定回調,實現想回調的方法即可,哪個注解不會用點進去看下源碼上的注釋

@OnTextChanged(value = R.id.mobileEditText, callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
void beforeTextChanged(CharSequence s, int start, int count, int after) {

}
@OnTextChanged(value = R.id.mobileEditText, callback = OnTextChanged.Callback.TEXT_CHANGED)
void onTextChanged(CharSequence s, int start, int before, int count) {

}
@OnTextChanged(value = R.id.mobileEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
void afterTextChanged(Editable s) {

}</code></pre>

代碼混淆

-keep class butterknife. { *; }
-dontwarn butterknife.internal.

-keep class *$$ViewBinder { ; }

-keepclasseswithmembernames class {
@butterknife.
<fields>;
}

-keepclasseswithmembernames class {
@butterknife.
<methods>;
}</code></pre>

Zelezny插件的使用

在AndroidStudio->File->Settings->Plugins->搜索Zelezny下載添加就行 ,可以快速生成對應組件的實例對象,不用手動寫。使用時,在要導入注解的Activity 或 Fragment 或 ViewHolder的layout資源代碼上,右鍵——>Generate——Generate ButterKnife Injections,然后就出現如圖的選擇框。

ButterKnife實現原理

對ButterKnife有過了解人 , 注入字段的方式是使用注解@BindView(R.id.tv_account_name),但首先我們需要在Activity聲明注入ButterKnife.bind(Activity activity) 。我們知道,注解分為好幾類, 有在源碼生效的注解,有在類文件生成時生效的注解,有在運行時生效的注解。分別為RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME ,其中以RetentionPolicy.RUNTIME最為消耗性能。而ButterKnife使用的則是編譯器時期注入,在使用的時候,需要配置classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’ , 這個配置說明,在編譯的時候,進行注解處理。要對注解進行處理,則需要繼承AbstractProcessor , 在boolean process(Set

ButterKnife實現方式

知曉了注解可以在編譯的時候進行處理,那么,我們就可以得到注解的字段屬性與所在類 , 進而生成注入文件,生成一個注入類的內部類,再進行字段處理 , 編譯之后就會合并到注入類中,達到植入新代碼段的目的。例如:我們注入@VInjector(R.id.tv_show) TextView tvShow;我們就可以得到tvShow這個變量與R.id.tv_show這個id的值,然后進行模式化處理injectObject.tvShow = injectObject.findViewById(R.id.tv_show); ,再將代碼以內部類的心事加入到組件所在的類中 , 完成一次DI(注入) 。

a) 首先創建一個視圖注解

b) 創建一個注解處理器,用來得到注解的屬性與所屬類

c) 解析注解,分離組合Class與屬性

d) 組合Class與屬性,生成新的Java File

APT生成的Java File , 以及模式代碼

使用Javac , 編譯時期生成注入類的子類

項目UML圖

簡要說明:

主要類:

VInjectProcessor —-> 注解處理器 , 需要配置注解處理器

resources

    - META-INF
          - services
                - javax.annotation.processing.Processor</code></pre> 

Processor內容:

com.myl.viewinject.apt.VInjectProcessor   # 指定處理器全類名

VInjectHandler —-> 注解處理類 , 主要進行注入類與注解字段進行解析與封裝,將同類的字段使用map集合進行映射。exp: Map

自定義ButterKnife具體實現

一 , 創建注解 , 對視圖進行注解,R.id.xxx , 所以注解類型是int類型

/**
 * Created by myl on 2016/11/21.
 *
 * View inject
 * 字段注入注解,可以新建多個注解,再通過AnnotationProcessor進行注解處理
 * RetentionPolicy.CLASS ,在編譯的時候進行注解 。我們需要在生成.class文件的時候需要進行處理
 */

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

二, 注解處理器 關于注解處理器配置,上面已經做了說明

/**
 * Created by myl on 2016/11/21.
 *
 * Inject in View annotation processor
 *
 * 需要在配置文件中指定處理類 resources/META-INF/services/javax.annotation.processing.Processor
 * com.myl.viewinject.apt.VInjectProcessor
 */

@SupportedAnnotationTypes("com.myl.viewinject.annotation.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {

    List<IAnnotationHandler> mAnnotationHandler = new ArrayList<>();
    Map<String,List<VariableElement>> mHandleAnnotationMap = new HashMap<>();
    private IGenerateAdapter mGenerateAdapter;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // init annotation handler , add handler
        registerHandler(new VInjectHandler());

        // init generate adapter
        mGenerateAdapter = new ViewGenerateAdapter(processingEnv);

    }

    /*可以有多個處理*/
    protected void registerHandler(IAnnotationHandler handler) {
        mAnnotationHandler.add(handler);
    }

    // annotation into process run
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        for (IAnnotationHandler handler : mAnnotationHandler) {
            // attach environment , 關聯環境
            handler.attachProcessingEnvironment(processingEnv);
            // handle annotation 處理注解 ,得到注解類的屬性列表
            mHandleAnnotationMap.putAll(handler.handleAnnotation(roundEnv));
        }
        // 生成輔助類
        mGenerateAdapter.generate(mHandleAnnotationMap);
        // 表示處理
        return true;
    }
}

對得到的注解進行處理 , 主要是進行注解類型與屬性進行分離合并處理,因為一個類有多個屬性,所以采用map集合,進行存儲,數據結構為:Map

/**
 * Created by myl on 2016/11/21.
 *
 * 注解處理實現 , 解析VInjector注解屬性
 */
public class VInjectHandler implements IAnnotationHandler {

    private ProcessingEnvironment mProcessingEnvironment;

    @Override
    public void attachProcessingEnvironment(ProcessingEnvironment environment) {
            this.mProcessingEnvironment = environment;
    }

    @Override
    public Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnvironment) {
        Map<String,List<VariableElement>> map = new HashMap<>();
        /*獲取一個類中帶有VInjector注解的屬性列表*/
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(VInjector.class);
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            /*獲取類名 ,將類目與屬性配對,一個類,對于他的屬性列表*/
            String className = getFullClassName(variableElement);
            List<VariableElement> cacheElements = map.get(className);
            if (cacheElements == null) {
                cacheElements = new ArrayList<>();
                map.put(className,cacheElements);
            }
            cacheElements.add(variableElement);
        }

        return map;
    }

    /**
     * 獲取注解屬性的完整類名
     * @param variableElement
     */
    private String getFullClassName(VariableElement variableElement) {
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String packageName = AnnotationUtils.getPackageName(mProcessingEnvironment,typeElement);
        return packageName+"."+typeElement.getSimpleName().toString();
    }
}

生成Java File , 根據獲取的屬性與類,創建一個注入類的內部類

/**
 * Created by myl on 2016/11/21.
 *
 * 生成View注解輔助類
 */
public class ViewGenerateAdapter extends AbstractGenerateAdapter {

    public ViewGenerateAdapter(ProcessingEnvironment processingEnvironment) {
        super(processingEnvironment);
    }

    @Override
    protected void generateImport(Writer writer, InjectInfo injectInfo) throws IOException {
        writer.write("package "+injectInfo.packageName+";");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.adapter.IVInjectorAdapter;");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.utils.ViewFinder;");
        writer.write("\n\n\n");
        writer.write("/* This class file is generated by ViewInject , do not modify */");
        writer.write("\n");
        writer.write("public class "+injectInfo.newClassName+" implements IVInjectorAdapter<"+injectInfo.className+"> {");
        writer.write("\n\n");
        writer.write("public void injects("+injectInfo.className+" target) {");
        writer.write("\n");
    }

    @Override
    protected void generateField(Writer writer, VariableElement variableElement, InjectInfo injectInfo) throws IOException {
        VInjector vInjector = variableElement.getAnnotation(VInjector.class);
        int resId = vInjector.value();
        String fieldName = variableElement.getSimpleName().toString();
        writer.write("\t\ttarget."+fieldName+" = ViewFinder.findViewById(target,"+resId+");");
        writer.write("\n");
    }

    @Override
    protected void generateFooter(Writer writer) throws IOException {
        writer.write(" \t}");
        writer.write("\n\n");
        writer.write("}");
    }
}

 

來自:http://www.androidchina.net/6082.html

 

 本文由用戶 zvci3409 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!