手把手教你Android自定義動(dòng)畫(新手向)

先來看最終效果圖

LineAnimation.gif

整個(gè)效果圖包括了

Share Element Transition
CollapsingToolbarLayout
AppbarLayout
自定義的動(dòng)畫效果

先來分析自定義動(dòng)畫煤蚌,用兩個(gè)TextView來分別顯示Title(動(dòng)物世界)和SubTitle(春天到了...)。SubTitle的動(dòng)畫需要自己手動(dòng)來畫髓窜,可以直接沿著TextView的周長(zhǎng)來畫線巢株。
我們需要重寫View的onDraw方法赡突,然后在方法體里來畫我們的圖像,然后通過調(diào)用invalidate來刷新View讓畫面動(dòng)起來。
線框的繪畫其實(shí)就是由5條線組合在一起赘被,這里用我用bottom,left,right,topLeft,topRight來代表這5條線悴侵,我們簡(jiǎn)單先給topLeft定義一個(gè)確定的長(zhǎng)度length 等于TextView的寬度1/4瞧剖。
為了讓這些白線有一定的粗細(xì),我們用canvas.drawRect來畫線而不是用drawLine可免,設(shè)定線的寬度stokeWidth抓于。


所以線框的完全體就是這樣

length = getWidth()/4;
stokeWidth = 2;
@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //bottom
        canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
        //left
        canvas.drawRect(0, 0, stokeWidth, getHeight(), paint);
        //right
        canvas.drawRect(getWidth() - stokeWidth, 0, getWidth(), getHeight(), paint);
        //topLeft
        canvas.drawRect(0, 0, length, stokeWidth, paint);
        //topRight
        canvas.drawRect(getWidth() - length, 0, getWidth(), stokeWidth, paint);
}

然后實(shí)現(xiàn)動(dòng)畫:
計(jì)算線框的總長(zhǎng)度(或者說繪制完成時(shí)的長(zhǎng)度) maxRound
動(dòng)畫過程中線框長(zhǎng)度 currentRound
設(shè)置繪制比例值 percent
通過比較currentRound的值畫出對(duì)應(yīng)幀的線框圖像

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float currentRound = maxRound * percent;
        if (currentRound <= stokeWidth) {
            return;
        }
        //currentRound長(zhǎng)度小于bottom時(shí),只畫出底部線條
        if (currentRound <= getWidth()) {
            canvas.drawRect((getWidth() - currentRound) / 2, getHeight() - stokeWidth, (getWidth() + currentRound) / 2, getHeight(), paint);
        //blr=bottom+left+right浇借,當(dāng)currentRound小于底邊和左右兩邊加起來的時(shí)候捉撮,畫出對(duì)應(yīng)的線條
        } else if (currentRound <= blr) {
            canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
            float y = getHeight() - (currentRound - getWidth()) / 2;
            canvas.drawRect(0, y, stokeWidth, getHeight(), paint);
            canvas.drawRect(getWidth() - stokeWidth, y, getWidth(), getHeight(), paint);
        } else {
            //bottom
            canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
            //left
            canvas.drawRect(0, 0, stokeWidth, getHeight(), paint);
            //right
            canvas.drawRect(getWidth() - stokeWidth, 0, getWidth(), getHeight(), paint);
            //blr為bottom,left妇垢,right加起來的長(zhǎng)度
            float r = (currentRound - blr) / 2;
            //topLeft
            canvas.drawRect(0, 0, r, stokeWidth, paint);
            //topRight
            canvas.drawRect(getWidth() - r, 0, getWidth(), stokeWidth, paint);
        }
    }
    //通過屬性動(dòng)畫來畫出幀動(dòng)畫
    public void animate(float f) {
        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setFloatValues(percent, f);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(this);
        valueAnimator.start();
    }
    //監(jiān)聽屬性動(dòng)畫回調(diào)改變繪畫的線框百分比percent
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        percent= (float) animation.getAnimatedValue();
        setTextColor(Color.argb((int) (percent * 255), 255, 255, 255));
        invalidate();
    }

順手給subTitle文字加個(gè)漸變巾遭,效果大概就是這樣


pic2.gif

接下來是讓動(dòng)畫和AppbarLayout的滑動(dòng)事件關(guān)聯(lián)起來
xml布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <com.facebook.drawee.view.SimpleDraweeView
                android:id="@+id/bgImageView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.5"
                tools:background="#cccccc" />

            <View
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#55000000" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:contentInsetLeft="0dp"
                app:contentInsetStart="0dp"
                app:layout_collapseMode="pin" />

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="48dp"
                android:maxLines="1"
                android:paddingLeft="8dp"
                android:paddingRight="8dp"
                android:textColor="@color/white"
                android:textSize="18dp"
                android:textStyle="bold"
                app:layout_collapseMode="parallax"
                tools:text="走進(jìn)科學(xué)" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <etong.lineanimation.SubTitleView
        android:id="@+id/subTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="58dp"
        android:gravity="center"
        android:maxLines="2"
        android:paddingBottom="12dp"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:paddingTop="20dp"
        android:textColor="@color/white"
        android:textSize="14dp"
        tools:text="春天到了肉康,又到了交配的季節(jié)。" />


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="show"
        android:layout_gravity="bottom"/>
</android.support.design.widget.CoordinatorLayout>

設(shè)置AppBarLayout監(jiān)聽
appBar.addOnOffsetChangedListener(this);
根據(jù)滑動(dòng)距離來和最大滑動(dòng)距離來計(jì)算線框繪制的百分比

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        if (!TextUtils.isEmpty(subTitle)) {
            float value = appBar.getHeight() - toolbar.getHeight() * 2;
            float p = (value + verticalOffset) / value;
            if (p < 0) {
                p = 0;
            }
            subTitleView.setPercent(p);
        }
   }

這里線框的位置是固定的灼舍,所以在appBarLayout滑動(dòng)的時(shí)候?yàn)榱吮苊饩€框出界吼和,需要線框提前一點(diǎn)消失,所以本來最大滑動(dòng)距離為
appBar.getHeight()-toolbar.getHeight()
改為
appBar.getHeight() - toolbar.getHeight() * 2
效果


pic3.gif

接下來需要處理一下SubTtileView本身高寬和主標(biāo)題Title所在TextView的寬度問題骑素。

  • Title最大行數(shù)為一行炫乓,SubTitle最大行數(shù)為兩行
  • SubTtileView本身的最大寬度也必須小于屏幕寬度,而且需要有最小的左右邊距 padding
  • SubTitleView的topLeft和topRight必須留有一點(diǎn)的長(zhǎng)度献丑,才能形成一個(gè)完整的線框 minLength
  • Title文字過多的時(shí)候會(huì)占滿屏幕末捣,所以必須限制Title的最大寬度
    maxTitleWidth = screenWidth - padding * 2 - minLength * 2
  • SubTitleView的寬度必須大于Title的寬度,所以我們要根據(jù)Title的寬度手動(dòng)計(jì)算SubTitleView的寬度
    minSubTitleViewWidth = titleWidth - minLenght * 2
  • ............
subtitleMargin = getResources().getDimensionPixelSize(R.dimen.subtitle_margin);
minLength = getResources().getDimensionPixelSize(R.dimen.min_length);
maxTitleWidth = ScreenUtils.screenWidth - subtitleMargin * 2 - minLength * 2;

    public void calculateSubTitle() {
        int titleWidth = titleTextView.getWidth();

        if (titleWidth > maxTitleWidth) {
            titleTextView.getLayoutParams().width = maxTitleWidth;
            subtitleView.getLayoutParams().width = maxTitleWidth + minLength * 2;
            subtitleView.setLength(minLength * 2);
            subtitleView.requestLayout();
            titleTextView.requestLayout();
        } else if (subtitleView.getWidth() > ScreenUtils.screenWidth - subtitleMargin * 2) {
            subtitleView.getLayoutParams().width = ScreenUtils.screenWidth - subtitleMargin * 2;
            subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
            subtitleView.requestLayout();
        } else if (subtitleView.getWidth() < titleWidth + minLength * 2) {
            subtitleView.getLayoutParams().width = titleWidth + minLength * 2;
            subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
            subtitleView.requestLayout();
        } else {
            subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
            subtitleView.requestLayout();
        }
    }

到此所有的自定義部分就寫完了创橄,剩下的過場(chǎng)動(dòng)畫由于是Android支持庫(kù)的api就不多解釋箩做。
最后,源碼鏈接 https://github.com/RoyWallace/LineAnimation
如果發(fā)現(xiàn)有錯(cuò)誤的地方或者不明白之處歡迎加群討論 qq群:295456349

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末筐摘,一起剝皮案震驚了整個(gè)濱河市卒茬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咖熟,老刑警劉巖圃酵,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異馍管,居然都是意外死亡郭赐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門确沸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捌锭,“玉大人,你說我怎么就攤上這事罗捎」矍” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵桨菜,是天一觀的道長(zhǎng)豁状。 經(jīng)常有香客問我,道長(zhǎng)倒得,這世上最難降的妖魔是什么泻红? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮霞掺,結(jié)果婚禮上谊路,老公的妹妹穿的比我還像新娘。我一直安慰自己菩彬,他們只是感情好缠劝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布潮梯。 她就那樣靜靜地躺著,像睡著了一般剩彬。 火紅的嫁衣襯著肌膚如雪酷麦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天喉恋,我揣著相機(jī)與錄音,去河邊找鬼母廷。 笑死轻黑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琴昆。 我是一名探鬼主播氓鄙,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼业舍!你這毒婦竟也來了抖拦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤舷暮,失蹤者是張志新(化名)和其女友劉穎态罪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體下面,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡复颈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沥割。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耗啦。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖机杜,靈堂內(nèi)的尸體忽然破棺而出帜讲,到底是詐尸還是另有隱情,我是刑警寧澤椒拗,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布似将,位于F島的核電站,受9級(jí)特大地震影響陡叠,放射性物質(zhì)發(fā)生泄漏玩郊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一枉阵、第九天 我趴在偏房一處隱蔽的房頂上張望译红。 院中可真熱鬧,春花似錦兴溜、人聲如沸侦厚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刨沦。三九已至诗宣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間想诅,已是汗流浹背召庞。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来破,地道東北人篮灼。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像徘禁,于是被迫代替她去往敵國(guó)和親诅诱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,755評(píng)論 22 665
  • 最近做了一個(gè)Android UI相關(guān)開源項(xiàng)目庫(kù)匯總送朱,里面集合了OpenDigg 上的優(yōu)質(zhì)的Android開源項(xiàng)目庫(kù)...
    OpenDigg閱讀 17,196評(píng)論 6 222
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評(píng)論 25 707
  • 小時(shí)候雪是一個(gè)懵懂無知的小女孩兒她輕柔娘荡,優(yōu)雅有時(shí),她很害怕害怕因?yàn)樽约鹤屨麄€(gè)多彩的世界迷失自我驶沼! 長(zhǎng)大后 ...
  • 昨天家里停電炮沐。本來想著燭火下寫字應(yīng)該也挺有氛圍,后來覺得桌上易燃物太多商乎,遂作罷央拖。 落花詩第二首: 飄飄蕩蕩復(fù)悠悠,...
    蘇白杞閱讀 525評(píng)論 0 2