Android Shape工具 Duck

photo

因爲本身在開發中遇到的問題,全部弄了一個 Android Shape 的工具 Duck ,能幫助開發者直接在 xml 的任意控件上實現 Shape 效果,無需建立額外的xml文件,而且沒有任何侵入性。java

廢話很少說,直接看效果:android

duck

項目地址: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 並未實現,由於有兩點考慮:

  1. shape 使用場景更多,而且更頻繁,其餘兩種只在少數特定場景中使用。
  2. selector 及 layout-list 須要更多精細的代碼控制,如所有擠在 xml 中一個控件上,會很是臃腫,難以維護。

原理

在考慮用什麼技術實現時,考慮這幾點:

  1. 任何控件都能有效,即便是自定義控件。
  2. 不能有侵入性,即便更換或廢棄本庫,也能保證穩定性。

最開始,第一個想到的是 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 系列 包含:

1.OOP 與 AOP

2.Java 註解處理器

3.Aspect

4.Android中使用 Javassist


因爲 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 的幾個經常使用子類,如LinearLayoutRelativeLayoutFrameLayout等,

因此,咱們咱們仿照 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 的能力遠不止如此,還有不少事情能夠作,建議你們能夠發揮想象,進行更多的擴展。


優化

核心原理已經搞定,還有兩點須要優化:

  1. 在 xml 中使用時,沒有提示不方便,這一點能夠經過Live Template 來解決。

    演示

  2. 沒法預覽,AOP 在編譯時工做,全部沒法實時預覽,看到別人的庫是替換成自定義 View 來查看效果,感受這種實現方式不夠完美,全部就放棄了,後續想着可否經過 AS 插件實現預覽。

項目地址:Duck


小插曲

這個庫的出現挺坎坷的。

在 18 年 8 月份左右,就開始寫了這個庫,當時核心功能的實現,xml 的代碼提示問題都已經想好了解決方案。

但寫到一半出現了功能相同的庫,

BackgroundLibrary

仔細看了項目代碼,對比發現實現原理不同,xml 提示解決方案相同,預覽問題經過替換解決。

額~

好吧!就瞬間沒寫下去的動力了,再加上公司趕工期,代碼就扔在那沒動了。

直到最近空閒了,忽然想着無論這樣,仍是弄完吧,搞個爛尾實在是很差。

相關文章
相關標籤/搜索