因爲本身在開發中遇到的問題,全部弄了一個 Android Shape 的工具 Duck ,能幫助開發者直接在 xml 的任意控件上實現 Shape 效果,無需建立額外的xml文件,而且沒有任何侵入性。java
廢話很少說,直接看效果:android
項目地址:Duckgit
這個庫的由來,是由於公司一個維護了 4 年的項目。github
經歷 4 年的項目,產品設計不知道改了多少版,期間產生並堆砌大量shape.xml
文件,這些文件由於索引的問題每每還沒法清理。編程
同時,同一個 shape.xml
文件,由於設計存在不規範的問題,在不一樣頁面改動了一點顏色、倒角或線寬等,就沒法複用,必須據此建立新的文件。app
最後,大量的文件堆積,開發人員開發時,花時間找 shape.xml
,還不如本身建立新的方便,這樣惡性循環, 只能 GG。工具
我想說,Android 設計 Shape
的初衷是好的: 一個 APP,統一的設計規範,就應該複用 Shape
。優化
但這種狀況對於國內的生態來講並不適用。spa
首先,相同屏幕尺寸,中文承載信息的能力遠大於英文,這就致使國外大部分 APP 界面設計簡潔清爽,國內就顯得很是複雜,同時國內互聯網更新速度很快,界面是生命週期短,人員流動,很難作到界面統一。插件
全部,Android 的 Shape 並不適合國內生態。
開發時,超級羨慕對面 IOS 開發們能夠直接在控件上進行花式倒角、加線框等騷操做,想不通爲啥 Android 不能在這一點上借鑑IOS。哎,Android 與 IOS 的宿命之爭,說多了都是淚。
基於上面種種緣由,因此出現了想開發這個庫。
這個庫只實現最經常使用的 Shape 功能,selector 及 layout-list 並未實現,由於有兩點考慮:
在考慮用什麼技術實現時,考慮這幾點:
最開始,第一個想到的是 LayoutInflater.Factory
,xml 控件解析成 View時,必須通過它,也是換膚的解決方案,但這樣得一個個替換成本身的,很是麻煩。
有沒有更好的解決方案呢?
得益於 AspectJ 的 AOP(面向切面編程)能力,咱們能夠在編譯時期,直接在 View 及其子類的構造方法中插入相關代碼,解析xml 中自定義的屬性,最後設置到控件上。
@Pointcut("execution(android.view.View+.new(..))")
public void callViewConstructor() {
}
@After("callViewConstructor()")
public void inject(JoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
Object target = joinPoint.getTarget();
Object[] args = joinPoint.getArgs();
int length = args.length;
if (!(target instanceof View) || length < 2 || target.hashCode() == lastHash || !(args[0] instanceof Context) || !(args[1] instanceof AttributeSet)) {
return;
}
lastHash = target.hashCode();
Context context = (Context) args[0];
AttributeSet attrs = (AttributeSet) args[1];
int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
Log.i(TAG, attrs.getAttributeName(i) + " = " + attrs.getAttributeValue(i));
}
Log.i(TAG, "inject =====> " + signature.toString());
DuckFactor.getFactor().inject((View) target, context, attrs);
}
複製代碼
AOP 相關內容,能夠查看AOP 系列 包含:
因爲 AspectJ 能遍歷項目中全部依賴包,所以,不管是 support 庫,仍是第三方庫都能獲得很好支持。
可是 AOP 也存在必定問題,咱們的 apk 中是不會存在系統原生 Android SDK 的,例如 TextView
這個系統控件,在編譯時是不會打包到 apk 中,所以,AOP 技術對這種原生控件無能爲力。
幸虧,咱們絕大部分項目爲了兼容性,通常都會直接依賴官方的兼容庫,即 support
相關的庫。
在 support· 庫中,會將一些原生控件,直接替換成 support 相關控件。相關代碼以下:
android/support/v7/app/AppCompatViewInflater switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
......
}
複製代碼
而對於這些控件,咱們的 AOP 都可以生效了。
在 support 庫中,沒有替換掉 ViewGroup 的幾個經常使用子類,如LinearLayout
、RelativeLayout
、FrameLayout
等,
因此,咱們咱們仿照 support 的替換方式,直接在 LayoutInflater.Factory.onCreateView
方法中注入相應的替換代碼。
@Pointcut("execution(* *..LayoutInflater.Factory+.onCreateView(..))")
public void callLayoutInflater() {
}
@Around("callLayoutInflater()")
public Object replaceView(ProceedingJoinPoint joinPoint) throws Throwable {
....
switch (name) {
case "RelativeLayout":
return new DuckRelativeLayout(context, attrs);
case "LinearLayout":
return new DuckLinearLayout(context, attrs);
case "FrameLayout":
return new DuckFrameLayout(context, attrs);
case "TableLayout":
return new DuckTableLayout(context, attrs);
case "ScrollView":
return new DuckScrollView(context, attrs);
default:
break;
}
return result;
}
複製代碼
這個庫的代碼其實不多,我這裏也只是實現了 Shape 這一個功能。
private static Injector mInjector;
public static void setFactor(Injector injector) {
mInjector = injector;
}
public static Injector getFactor() {
if (mInjector == null) {
mInjector = new ShapeInjector();
}
return mInjector;
}
複製代碼
這裏保留的 Duck 的擴展性,若是以爲不夠,能夠自行實現功能更強大的 Injector。
AOP 的能力遠不止如此,還有不少事情能夠作,建議你們能夠發揮想象,進行更多的擴展。
核心原理已經搞定,還有兩點須要優化:
在 xml 中使用時,沒有提示不方便,這一點能夠經過Live Template 來解決。
沒法預覽,AOP 在編譯時工做,全部沒法實時預覽,看到別人的庫是替換成自定義 View 來查看效果,感受這種實現方式不夠完美,全部就放棄了,後續想着可否經過 AS 插件實現預覽。
項目地址:Duck
這個庫的出現挺坎坷的。
在 18 年 8 月份左右,就開始寫了這個庫,當時核心功能的實現,xml 的代碼提示問題都已經想好了解決方案。
但寫到一半出現了功能相同的庫,
仔細看了項目代碼,對比發現實現原理不同,xml 提示解決方案相同,預覽問題經過替換解決。
額~
好吧!就瞬間沒寫下去的動力了,再加上公司趕工期,代碼就扔在那沒動了。
直到最近空閒了,忽然想着無論這樣,仍是弄完吧,搞個爛尾實在是很差。