從未見過如此美麗動人的CardView

故事還得從看到那張動圖說起痢法。

像往常一樣凉夯,休息時間我都會打開uplabs瀏覽一下國外大佬們的UI設(shè)計。

有個設(shè)計十分吸引眼球垢粮,就是下圖贴届。

仔細看每張圖片,在加載出來的時候背景都會有一個偏移的動效蜡吧,簡約而不簡單毫蚓。

這個能實現(xiàn)嗎?如果公司UI團隊給了這么一個效果圖昔善,你該咋辦元潘?

思路

首先說說思路,既然要做君仆,顯得有個載體吧翩概,可能很多同學(xué)一下子就想到了ImageView這個東西。但現(xiàn)在是設(shè)么年代了返咱?Material Design的呀钥庇,所以再用ImageView是不是有點low了。所以自然想到就應(yīng)該是CardView嘛咖摹。

但CardView有個蛋疼的設(shè)定评姨,不能設(shè)置背景圖片,不知道小伙伴們發(fā)現(xiàn)了沒有楞艾?

stackoverflow上的答案過于簡單粗暴参咙,不是我的菜龄广。

既然不用這種方法硫眯,那我們只能使用我們的神器onDraw了,從根本上解決問題择同。

代碼

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if ((tobePaint!=null&&!tobePaint.isRecycled())) {
            canvas.drawBitmap(tobePaint, backgroundSubRec, backgroundRec, paint);
        }
    }

onDraw方法很簡單两入,就是當(dāng)有可繪制背景的時候就去繪制。

這里有四個變量要關(guān)注一下:

  • tobePaint: 需要被繪制的Bitmap對象敲才。
  • backgroundSubRec:Rect對象裹纳,表示Bitmap中需要被繪制的區(qū)域择葡。后續(xù)就是通過改變這個變量來達到動畫效果。
  • backgroundRec:Rect對象剃氧,表示繪制區(qū)域的大小敏储,大小同CardView的大小。

最開始我們自定義的這個視圖與普通的CardView沒有差異朋鞍,當(dāng)調(diào)用完public void enableActivation(Bitmap activationBg, String key)這個方法后已添,背景就被繪制上去了,如下圖滥酥。

來看看代碼

    public void enableActivation(Bitmap activationBg, String key) {
        currentKey = key;
        isActivation = false;
        init(activationBg,key);
    }

具體看init這個方法:

private void init(final Bitmap originBitmap,final String key) {
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                getViewTreeObserver().removeOnPreDrawListener(this);
                Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        w = getWidth();
                        h = getHeight();

                        int scaledW = (int) (w * bgScale);
                        int scaledH = (int) (h * bgScale);

                        double preSH = 1.0 * originBitmap.getHeight() / scaledH;
                        double preSW = 1.0 * originBitmap.getWidth() / scaledW;

                        float smallPreS = (float) Math.min(preSH, preSW);

                        Matrix matrix = new Matrix();
                        float s = 1 / smallPreS;
                        matrix.postScale(s, s);
                        if(sIsEnableCache){
                            background = sCache.get(key);
                            if(background == null){
                                background = Bitmap.createBitmap(originBitmap, 0, 0, originBitmap.getWidth(), originBitmap.getHeight(), matrix, true);
                                sCache.put(key,background);
                            }
                        }else {
                            background = Bitmap.createBitmap(originBitmap, 0, 0, originBitmap.getWidth(), originBitmap.getHeight(), matrix, true);
                        }


                        defaultLeft = (background.getWidth() - w) / 2;
                        defaultTop = (background.getHeight() - h) / 2;
                        backgroundRec = new Rect(0, 0, w, h);
                        backgroundSubRec = new Rect(defaultLeft, defaultTop, w + defaultLeft, h + defaultTop);
                        currentPosition = POSITION_CENTER;
                        tobePaint = background;
                        Log.d("scott"," key = " + key + "    current key = " + currentKey);
                        if(key.equals(currentKey)){
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    invalidate();
                                    isActivation = true;
                                }
                            });
                        }

                    }
                });

                return true;
            }
        });
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        invalidate();
    }

這個方法做了這么幾件事:

  • 確定CardView的長寬更舞。
  • 根據(jù)CardView的實際大小對傳入的Bitmap進行適當(dāng)放大,為后續(xù)動畫做準備坎吻。
  • 確定backgroundRec缆蝉,backgroundSubRec這兩個對象的值。
  • 給tobePaint對象賦值瘦真。
  • 調(diào)用invalidate()繪制背景刊头。

為了方便理解,我畫了如下這張圖吗氏。

下面是動畫部分芽偏,這部分。

先來說說原理弦讽,上面繪制的圖像是靠backgroundSubRec對tobePaint進行截取而來的污尉,一開始backgroundSubRec截取的是放大后tobePaint的中間部分,其大小和CardView一致往产,接著通過不斷的改變backgroundSubRec的值被碗,讓其慢慢向右移動。來截取tobePaint的右邊部分仿村。

下面是代碼:

public void postRight() {

        if (!isActivation) {
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    postRight();
                }
            }, 1000 / 60);
            return;
        }

        if (currentPosition == POSITION_INVAL || currentPosition == POSITION_RIGHT) {
            Log.d("scott", "current position is already right");
            return;
        }

        currentPosition = POSITION_INVAL;
        final int delta = defaultLeft * 2 - backgroundSubRec.left;
        int tempStep = delta / animationDuration;
        if (tempStep == 0) tempStep = 1;
        final int step = tempStep;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if(!isActivation) return;
                invalidate();
                if (backgroundSubRec.left < defaultLeft * 2) {
                    backgroundSubRec.left += step;
                    backgroundSubRec.right += step;
                    handler.postDelayed(this, fps);
                } else {
                    currentPosition = POSITION_RIGHT;
                }
            }
        }, fps);
    }

最后是效果圖锐朴。


最后

雖然上面講的比較簡單,其實在這過程中有一些細節(jié)還是需要注意的蔼囊,比如bitmap的格式最好使用RGB_565來減少內(nèi)存占用焚志,使用LruCahce來緩存Bitmap增加背景切換速度,還有就是背景放大的比例也需要根據(jù)實際需求做調(diào)整畏鼓。

下面給出代碼酱酬,

github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市云矫,隨后出現(xiàn)的幾起案子膳沽,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挑社,死亡現(xiàn)場離奇詭異陨界,居然都是意外死亡,警方通過查閱死者的電腦和手機痛阻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門菌瘪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阱当,你說我怎么就攤上這事麻车。” “怎么了斗这?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵动猬,是天一觀的道長。 經(jīng)常有香客問我表箭,道長赁咙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任免钻,我火速辦了婚禮彼水,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘极舔。我一直安慰自己凤覆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布拆魏。 她就那樣靜靜地躺著盯桦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渤刃。 梳的紋絲不亂的頭發(fā)上拥峦,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音卖子,去河邊找鬼略号。 笑死,一個胖子當(dāng)著我的面吹牛洋闽,可吹牛的內(nèi)容都是我干的玄柠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼诫舅,長吁一口氣:“原來是場噩夢啊……” “哼羽利!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骚勘,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤铐伴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俏讹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體当宴,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年泽疆,在試婚紗的時候發(fā)現(xiàn)自己被綠了旨别。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空执。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出混稽,到底是詐尸還是另有隱情,我是刑警寧澤炒瘟,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布黎比,位于F島的核電站,受9級特大地震影響眠砾,放射性物質(zhì)發(fā)生泄漏虏劲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一褒颈、第九天 我趴在偏房一處隱蔽的房頂上張望柒巫。 院中可真熱鬧,春花似錦谷丸、人聲如沸堡掏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉唁。三九已至,卻和暖如春揩慕,著一層夾襖步出監(jiān)牢的瞬間游两,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工漩绵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贱案,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓止吐,卻偏偏與公主長得像宝踪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碍扔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容