告別.xml檔案,用AOP方式實現shape,selector
網上關於替代在.xml檔案裡寫shape、selector標籤的方式有很多,為什麼還要自己再寫一篇呢? 最近在學習AOP,就想著能否用AOP的方式來實現能,於是就有了這篇文章。主要目的是提供另外一種不同的實現方式,也給學習AOP的同學一些參考。
效果圖

BgDrawable.gif
使用方法
在主Module的build.grade檔案中新增
apply plugin: 'android-aspectjx' dependencies { ... implementation 'com.stubborn:bgdrawable:1.0.0' }
在專案根目錄的build.grade檔案中新增
dependencies { ... classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.2' }
在Activity或Fragment中,在控制元件上添加註解@BgDrawable,示例如下:
@BgDrawable(id = R.id.button, color = R.color.colorPrimary, cornerRadius = 20, strokeWidth = 3, strokeColor = R.color.colorAccent, pressedColor = R.color.colorAccent ) private Button button;
注意這裡的id就是控制元件的id,要確保和正確,否則會沒有效果。strokeWidth等屬性的單位都是dp,color相關的屬性必須是R.color.xxx形式。
支援的屬性有以下這些:
int id() default 0; int shape() default GradientDrawable.RECTANGLE; int color() default 0; int alpha() default 255; float cornerRadius() default 0; float topleftRadius() default 0; float toprightRadius() default 0; float bottomleftRadius() default 0; float bottomrightRadius() default 0; float width() default 0; float height() default 0; float strokeWidth() default 0; int strokeColor() default 0; float dashWidth() default 0; float dashGap() default 0; GradientDrawable.Orientation orientation() default GradientDrawable.Orientation.TOP_BOTTOM; int startColor() default 0; int centerColor() default 0; int endColor() default 0; int gradientType() default GradientDrawable.LINEAR_GRADIENT; float gradientCenterX() default 0; float gradientCenterY() default 0; float gradientRadius() default 0; int pressedColor() default 0; int selectedColor() default 0; int checkedColor() default 0; int focusedColor() default 0; int unabledColor() default 0; int pressedStrokeColor() default 0; int selecteStrokedColor() default 0; int checkedStrokeColor() default 0; int focusedStrokeColor() default 0; int unabledStrokeColor() default 0;
只要在@BgDrawable裡新增這些屬性就可以實現shape和selector效果,是不是很簡單呢。
實現方式
以Activity為例進行說明,程式碼如下:
public class MainActivity extends AppCompatActivity { @BgDrawable(id = R.id.button, color = R.color.colorPrimary, cornerRadius = 20, strokeWidth = 3, strokeColor = R.color.colorAccent, pressedColor = R.color.colorAccent, ) private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); } }
一般在Activity中,要獲取一個控制元件我們需要呼叫Activity的findViewById方法,所以我們可以用AspectJ來對findViewById方法進行程式碼注入。
程式碼如下:
@Aspect public class BgDrawableAspect { @Around("call(* android.app.Activity.findViewById(..))") public Object executionfindViewById(ProceedingJoinPoint joinPoint) throws Throwable { Object returnValue = joinPoint.proceed();//findViewById的返回值 Field[] fields = joinPoint.getThis().getClass().getDeclaredFields();//MainActivity的成員變數 for(Field field : fields){ field.setAccessible(true); BgDrawable anno = field.getAnnotation(BgDrawable.class);//獲取@BgDrawable註解 if(anno!=null && returnValue!=null) { View view = (View) returnValue; if(view.getId()==anno.id()) { Drawable drawable = BgDrawableUtil.getDrawable(view.getContext(), anno); view.setBackground(drawable); break; } } } return returnValue; } }
在上面的程式碼中,通過joinPoint.proceed()獲取findViewById的返回值,也即是我們的目標控制元件,接下來我們需要獲取MainActivity的成員變數,從中找帶有@BgDrawable註解成員變數,如果註解中的id與目標控制元件的id相同,那麼就從@BgDrawable註解中獲取設定的屬性,來建立一個Drawable物件,最後把Drawable設定為目標控制元件的背景。
以上就是Activity中實現的過程,在Fragment中實現過程大致相同,只是把要注入的方法換成view的findViewById方法即android.view.View.findViewById(..)。可能有人會問,我沒有使用findViewById方法,使用的是Butterknife怎麼辦,還能支援嗎?請接著往下看。
對Butterknife的支援
下面是使用Butterknife的Activity示例:
public class ButterknifeActivity extends AppCompatActivity { @BgDrawable(id = R.id.button, color = R.color.colorAccent, cornerRadius = 10, pressedColor = R.color.colorPrimary ) @BindView(R.id.button) Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_butterknife); ButterKnife.bind(this); } }
上面使用Butterknife的ButterknifeActivity類,在編譯時生成一個叫ButterknifeActivity_ViewBinding的.class檔案,
public class ButterknifeActivity_ViewBinding implements Unbinder { private ButterknifeActivity target; @UiThread public ButterknifeActivity_ViewBinding(ButterknifeActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public ButterknifeActivity_ViewBinding(ButterknifeActivity target, View source) { this.target = target; target.button = (Button)Utils.findRequiredViewAsType(source, 2131165218, "field 'button'", Button.class); } @CallSuper public void unbind() { ButterknifeActivity target = this.target; if (target == null) { throw new IllegalStateException("Bindings already cleared."); } else { this.target = null; target.button = null; } } }
我們來看下ButterknifeActivity_ViewBinding的構造方法,引數target就是ButterknifeActivity的例項物件,Utils.findRequiredViewAsType方法可以獲取到我們的目標控制元件,它的內部也是通過view的findViewById方法實現的,最終賦值給了target.button,也就是activity的成員變數button。有了Activity的例項物件和目標控制元件,如果我們對這個構造方法進行程式碼注入,不就可以實現我們想要的功能了嗎。
@Aspect public class BgDrawableAspect { @After("initialization(* ..*_ViewBinding.new(..))") public void executionbButterknife(JoinPoint joinPoint) throws Throwable { Object target = joinPoint.getArgs()[0]; //獲取第一個引數,這裡指Activity的例項物件 Field[] fields = target.getClass().getDeclaredFields(); //獲取Activity的成員變數 for(Field field : fields){ field.setAccessible(true); Object fieldValue = field.get(target); BgDrawable anno = field.getAnnotation(BgDrawable.class); if (anno != null && fieldValue instanceof View) { View view = (View) fieldValue; if (view.getId() == anno.id()) { Drawable drawable = BgDrawableUtil.getDrawable(view.getContext(), anno); view.setBackground(drawable); } } } } }
上面的程式碼中,initialization表示對構造方法進行注入,@After表示在原方法之後注入,所以執行上面程式碼的時候activity的成員變數target.button已經被賦值了,就是我們的目標控制元件,我們再獲取這個成員變數上的@BgDrawable註解,根據註解的屬性來建立一個Drawable物件,最後把Drawable設定為target.button的背景就可以了。
具體實現方式請看 原始碼

歡迎關注.jpg