JJSearchViewAnim源碼分析

項(xiàng)目地址:JJSearchViewAnim,本文分析版本: 3a19d94

1.簡(jiǎn)介

icon

JJSearchViewAnimCJJ同學(xué)這周剛放出來(lái)的一個(gè)實(shí)現(xiàn)了各種搜索交互動(dòng)畫的動(dòng)畫庫(kù),一共實(shí)現(xiàn)了8種不同的搜索交互動(dòng)畫,短短4天github上的star就已經(jīng)900+??梢姶隧?xiàng)目的受歡迎程度。我也第一時(shí)間把代碼clone下來(lái)看了一遍,并和CJJ交流了一些心得,這篇文章我們就來(lái)分析JJSearchViewAnim到底是如何實(shí)現(xiàn)的,以及該怎么更好的運(yùn)用的項(xiàng)目中去呢?

2.使用方法

JJSearchViewAnim實(shí)現(xiàn)的效果部分如下,更詳細(xì)的請(qǐng)參照這里:

JJDotGoPathController
JJAroundCircleBornTailController
JJCircleToLineAlphaController

JJSearchViewAnim使用方法相當(dāng)簡(jiǎn)單:

1.先在布局文件xml中聲明


<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.cjj.jjsearchviewanim.MainActivity">

    <com.cjj.sva.JJSearchView
        android:id="@+id/jjsv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
 </RelativeLayout>

2.再在Java代碼中設(shè)置你需要顯示的動(dòng)畫類型


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        JJSearchView mJJSearchView = (JJSearchView) findViewById(R.id.jjsv);
        mJJSearchView.setController(new JJChangeArrowController());
    }

3.設(shè)置動(dòng)畫開啟及恢復(fù)


mJJSearchView.startAnim();
mJJSearchView.resetAnim();

3.類關(guān)系圖

JJSearchViewAnim.png

從上面的類圖中可以清晰的看到JJSearchViewAnim的項(xiàng)目結(jié)構(gòu):

JJSearchView是繼承自View的,其內(nèi)部持有一個(gè)JJBaseController的對(duì)象,JJBaseController有8個(gè)子類的實(shí)現(xiàn),應(yīng)該就是對(duì)應(yīng)這8個(gè)動(dòng)畫的具體實(shí)現(xiàn)了。通過(guò)給JJSearchView設(shè)置不同的Controller就能實(shí)現(xiàn)對(duì)應(yīng)的動(dòng)畫效果了。

是不是覺得跟我們以前分析過(guò)的HTextView很相似?因?yàn)檫@兩個(gè)項(xiàng)目都使用了策略模式來(lái)設(shè)定不同的Controller從而實(shí)現(xiàn)不同的動(dòng)畫效果,如果你以后也想開發(fā)這種類型的項(xiàng)目,那么這種架構(gòu)是相當(dāng)適合你的。

4.源碼分析

在分析源碼之前,我們要知道其實(shí)所有的動(dòng)畫無(wú)非是:

在規(guī)定的動(dòng)畫持續(xù)時(shí)間內(nèi),在特定時(shí)間繪制出當(dāng)前需要展示的畫面,并隨時(shí)間變化而改變繪制的畫面從而形成動(dòng)畫。

我們?cè)陂_發(fā)中使用屬性動(dòng)畫時(shí),我們只需要傳遞需要變換的參數(shù)和時(shí)間等等,Android已經(jīng)為我們封裝好了繪制過(guò)程。但是如果我們需要開發(fā)例如上圖中的幾個(gè)效果的時(shí)候,屬性動(dòng)畫已經(jīng)不能滿足我們,這時(shí)候我們就要負(fù)責(zé)整個(gè)動(dòng)畫每一幀的繪制了。這就要運(yùn)用到自定義View,Canvas,Paint,PathPathMeasure等等相關(guān)知識(shí)了。那到底該如何實(shí)現(xiàn)呢?下面我們就先從整體上分析JJSearchView的整體結(jié)構(gòu),然后再具體分析兩個(gè)動(dòng)畫的具體實(shí)現(xiàn),相信你看完之后就會(huì)明白。

1.JJSearchView的實(shí)現(xiàn)

JJSearchView的部分代碼如下:


public class JJSearchView extends View {
    private JJBaseController mController = new JJChangeArrowController();

    public JJSearchView(Context context) {
        this(context, null);
    }

    public JJSearchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public void setController(JJBaseController controller) {
        this.mController = controller;
        mController.setSearchView(this);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mController.draw(canvas, mPaint);
    }

    public void startAnim() {
        if (mController != null)
            mController.startAnim();
    }

    public void resetAnim() {
        if (mController != null)
            mController.resetAnim();
    }

}

可以看出在onDraw()方法中直接調(diào)用了JJBaseControllerdraw()方法說(shuō)明具體的繪制都是交給JJBaseController的實(shí)現(xiàn)類去做的,同時(shí)又提供了startAnim()resetAnim()也都是調(diào)用mController了對(duì)應(yīng)方法。代碼很簡(jiǎn)單就不再多說(shuō)了,我們繼續(xù)來(lái)看看JJBaseController

2.JJBaseController的實(shí)現(xiàn)

JJBaseController的部分代碼如下:


public abstract class JJBaseController {

    public abstract void draw(Canvas canvas, Paint paint);

    //開啟搜索動(dòng)畫
    public void startAnim() {
    }

    //重置搜索動(dòng)畫
    public void resetAnim() {
    }

    public ValueAnimator startSearchViewAnim() {
        ValueAnimator valueAnimator = startSearchViewAnim(0, 1, 500);
        return valueAnimator;
    }

    public ValueAnimator startSearchViewAnim(float startF, float endF, long time) {
        ValueAnimator valueAnimator =startSearchViewAnim(startF, endF, time, null);
        return valueAnimator;
    }

    public ValueAnimator startSearchViewAnim(float startF, float endF, long time, final PathMeasure pathMeasure) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(startF, endF);
        valueAnimator.setDuration(time);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mPro = (float) valueAnimator.getAnimatedValue();
                if (null != pathMeasure)
                    pathMeasure.getPosTan(mPro, mPos, null);
                getSearchView().invalidate();
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        if (!valueAnimator.isRunning()) {
            valueAnimator.start();
        }
        mPro = 0;
        return valueAnimator;
    }
}

JJBaseController是一個(gè)抽象類,draw(Canvas canvas, Paint paint);方法是一個(gè)抽象方法,所以實(shí)現(xiàn)類必須實(shí)現(xiàn)draw()方法來(lái)完成具體動(dòng)畫的繪制,此外startAnim()resetAnim()也是空方法子類可以重寫實(shí)現(xiàn)具體的功能。
值得注意的是這里實(shí)現(xiàn)的一個(gè)ValueAnimator,從代碼上看上去是一個(gè)在500毫秒中從0-1的一個(gè)不斷變化的值,最后在onAnimationUpdate()回調(diào)方法中賦值給了mPro并調(diào)用了invalidate();方法。從這里應(yīng)該可以看出,在JJSearchViewAnim中所有具體的Controller應(yīng)該都是根據(jù)不斷變化的mPro的值來(lái)繪制對(duì)應(yīng)的圖像,最終形成動(dòng)畫。

此外JJBaseController還定義了三種狀態(tài):


    public static final int STATE_ANIM_NONE = 0;
    public static final int STATE_ANIM_START = 1;
    public static final int STATE_ANIM_STOP = 2;

    @IntDef({STATE_ANIM_NONE,STATE_ANIM_START, STATE_ANIM_STOP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

分別對(duì)應(yīng)當(dāng)前View無(wú)動(dòng)畫,動(dòng)畫開始和動(dòng)畫結(jié)束的狀態(tài)。下面我們就具體分析兩個(gè)Controller的具體實(shí)現(xiàn),來(lái)看看具體怎么實(shí)現(xiàn)的,分別是JJAroundCircleBornTailControllerJJCircleToLineAlphaController。

3.JJAroundCircleBornTailController的實(shí)現(xiàn)

在看具體的實(shí)現(xiàn)之前,我們先看一下這個(gè)動(dòng)畫的設(shè)計(jì)圖:

JJAroundCircleBornTailController

我們可以分解成兩種狀態(tài):

  1. 正常狀態(tài):就是一個(gè)搜索放大鏡。
  2. 動(dòng)畫狀態(tài):圓弧形進(jìn)度不斷圍繞圓環(huán)旋轉(zhuǎn),并且進(jìn)度完成之后放大鏡的把手不斷變長(zhǎng)最終變成和正常狀態(tài)一樣。

再來(lái)看看具體的代碼實(shí)現(xiàn):


public class JJAroundCircleBornTailController extends JJBaseController {
    private int mAngle = 10;
    private RectF mRectF;
    private int cx, cy, cr;

    @Override
    public void draw(Canvas canvas, Paint paint) {
        //先設(shè)置一個(gè)背景
        canvas.drawColor(Color.parseColor(mColor));
        //根據(jù)當(dāng)前狀態(tài)的不同調(diào)用不同的繪制方法
        switch (mState) {
            case STATE_ANIM_NONE:
                drawNormalView(paint, canvas);
                break;
            case STATE_ANIM_START:
                drawStartAnimView(paint, canvas);
                break;
            case STATE_ANIM_STOP:
                drawStopAnimView(paint, canvas);
                break;
        }
    }

    private void drawStartAnimView(Paint paint, Canvas canvas) {
        //設(shè)置paint的狀態(tài)
        paint.setAntiAlias(true);
        paint.setColor(Color.parseColor(mColorTran));
        paint.setStrokeWidth(10);
        paint.setStyle(Paint.Style.STROKE);
        canvas.rotate(45, cx, cy);
        //繪制旋轉(zhuǎn)時(shí)的外環(huán)
        canvas.drawCircle(cx, cy, cr, paint);
        //給mRectF賦值為圓形成的矩形的值
        mRectF.left = cx - cr;
        mRectF.right = cx + cr;
        mRectF.top = cy - cr;
        mRectF.bottom = cy + cr;

        //當(dāng)mPro小于0.2時(shí),繪制一個(gè)不斷變短的直線以及一個(gè)弧形
        if (mPro <= 0.2) {
            canvas.drawLine(cx + cr, cy, cx + cr + cr * (.2f - mPro),
                    cy, paint);
            canvas.save();
            paint.setAntiAlias(true);
            paint.setColor(Color.WHITE);
            canvas.drawArc(mRectF, 6, -14, false, paint);
            canvas.restore();
        } else if (mPro > 0.2 && mPro < 4.5) {
            canvas.save();
            paint.setColor(Color.WHITE);
            //不斷增加mAngle的值
            mAngle += 20;
            //不斷的旋轉(zhuǎn)畫布再繪制弧形,就可以形成旋轉(zhuǎn)進(jìn)度
            canvas.rotate(mAngle, getWidth() / 2, getHeight() / 2);
            canvas.drawArc(mRectF, 0, mAngle / 4, false, paint);
            canvas.restore();
        } else {
            //當(dāng)mPro的值大于4.5時(shí)
            canvas.save();
            paint.setAntiAlias(true);
            paint.setColor(Color.WHITE);
            paint.setStrokeWidth(14);
            paint.setStyle(Paint.Style.STROKE);
            //繪制出放大鏡的把手,這里通過(guò)mPro來(lái)時(shí)把手的長(zhǎng)度不斷增加
            canvas.drawLine(cx + cr, cy, cx + cr + cr * ((mPro - 4.5f) * 2), cy, paint);
            canvas.drawCircle(cx, cy, cr, paint);
            canvas.restore();
        }

    }

    private void drawNormalView(Paint paint, Canvas canvas) {
        //cr 表示圓環(huán)半徑
        cr = getWidth() / 15;
        //cx 表示圓心的x坐標(biāo)
        cx = getWidth() / 2;
        //cy 表示圓心得y坐標(biāo)
        cy = getHeight() / 2;
        paint.reset();
        paint.setAntiAlias(true);
        // 保存當(dāng)前canvas的狀態(tài)
        canvas.save();
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(14);
        paint.setStyle(Paint.Style.STROKE);
        //將canvas旋轉(zhuǎn)45度
        canvas.rotate(45, cx, cy);
        //畫斜線
        canvas.drawLine(cx + cr, cy, cx + cr * 2, cy, paint);
        //畫圓形
        canvas.drawCircle(cx, cy, cr, paint);
        //恢復(fù)canvas的狀態(tài)到上次save()方法調(diào)用的狀態(tài)
        canvas.restore();
    }

    @Override
    public void startAnim() {
        if (mState == STATE_ANIM_START) return;
        //設(shè)置狀態(tài)
        mState = STATE_ANIM_START;
        //開啟ValueAnimator
        startSearchViewAnim(0, 5, 2000);
    }

    @Override
    public void resetAnim() {
        if (mState == STATE_ANIM_STOP) return;
        mState = STATE_ANIM_STOP;
        mAngle = 0;
        startSearchViewAnim();
    }
}

以上就是大部分JJAroundCircleBornTailController的代碼,由于這個(gè)動(dòng)畫的初始狀態(tài)和完成動(dòng)畫后的狀態(tài)是一樣的所以drawStopAnimView(paint, canvas);drawStartAnimView(paint, canvas);方法是相同的實(shí)現(xiàn),這里就省略了。

從上面的代碼注釋中可以看出,當(dāng)我們調(diào)用startAnim()方法時(shí)會(huì)通過(guò)startSearchViewAnim(0, 5, 2000);開啟ValueAnimator,這里是在2000毫秒中將mPro的值從0-5勻速變換,然后再回調(diào)方法中又回不斷的調(diào)用invalidate()方法從而不斷調(diào)用JJAroundCircleBornTailControllerdraw()方法,進(jìn)而就可以通過(guò)判斷mPro的值來(lái)繪制不同狀態(tài)的圖像。從而就達(dá)到了動(dòng)畫效果。相當(dāng)清晰的實(shí)現(xiàn),下面讓我們來(lái)看看JJCircleToLineAlphaController是不是也是類似的實(shí)現(xiàn)方法呢?

4.JJCircleToLineAlphaController的實(shí)現(xiàn)

再看一下這次的動(dòng)畫設(shè)計(jì)圖:

JJCircleToLineAlphaController

同樣可以分解成兩種狀態(tài):

  1. 正常狀態(tài): 一個(gè)放大鏡以及外面有一個(gè)圓環(huán)
  2. 動(dòng)畫狀態(tài): 整體不斷向右平移,并且圓環(huán)會(huì)不斷減少最后變?yōu)檩斎肟虻臋M線。

下面我們來(lái)看看具體實(shí)現(xiàn):


public class JJCircleToLineAlphaController extends JJBaseController {
    private String mColor = "#673AB7";
    private int cx, cy, cr;
    private RectF mRectF, mRectF2;
    private float sign = 0.707f;
    private float tran = 120;

    public JJCircleToLineAlphaController() {
        mRectF = new RectF();
        mRectF2 = new RectF();
    }

    @Override
    public void draw(Canvas canvas, Paint paint) {
        canvas.drawColor(Color.parseColor(mColor));
        switch (mState) {
            case STATE_ANIM_NONE:
                drawNormalView(paint, canvas);
                break;
            case STATE_ANIM_START:
                drawStartAnimView(paint, canvas);
                break;
            case STATE_ANIM_STOP:
                drawStopAnimView(paint, canvas);
                break;
        }
    }

    private void drawStopAnimView(Paint paint, Canvas canvas) {
        canvas.save();
        if (mPro > 0.7) {
            paint.setAlpha((int) (mPro * 255));
            drawNormalView(paint, canvas);
        }
        canvas.restore();
    }

    private void drawStartAnimView(Paint paint, Canvas canvas) {
        ...
    }

    private void drawNormalView(Paint paint, Canvas canvas) {
        ...
    }

    @Override
    public void startAnim() {
        if (mState == STATE_ANIM_START) return;
        mState = STATE_ANIM_START;
        startSearchViewAnim();
    }

    @Override
    public void resetAnim() {
        if (mState == STATE_ANIM_STOP) return;
        mState = STATE_ANIM_STOP;
        startSearchViewAnim();
    }
}

好像和第一個(gè)動(dòng)畫是一個(gè)套路是嗎?是的,其實(shí)這些動(dòng)畫經(jīng)過(guò)我們的分析,無(wú)非是兩種或三種狀態(tài),再根據(jù)動(dòng)畫期間不斷變換的mPro的值再做具體的動(dòng)畫就可以了,所以我們?cè)诰唧w看看這里的drawNormalView(Paint paint, Canvas canvas);方法和drawStartAnimView(Paint paint, Canvas canvas);方法的實(shí)現(xiàn):


    private void drawNormalView(Paint paint, Canvas canvas) {
        //圓的半徑
        cr = getWidth() / 50;
        //圓心x坐標(biāo)
        cx = getWidth() / 2;
        //圓心y坐標(biāo)
        cy = getHeight() / 2;
        //內(nèi)圓所占的矩形區(qū)域
        mRectF.left = cx - cr;
        mRectF.right = cx + cr;
        mRectF.top = cy - cr;
        mRectF.bottom = cy + cr;
        //外圓所占的矩形局域
        mRectF2.left = cx - 3 * cr;
        mRectF2.right = cx + 3 * cr;
        mRectF2.top = cy - 3 * cr;
        mRectF2.bottom = cy + 3 * cr;

        canvas.save();
        paint.reset();
        paint.setAntiAlias(true);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);

        canvas.rotate(45, cx, cy);
        //繪制放大鏡把手
        canvas.drawLine(cx + cr, cy, cx + cr * 2, cy, paint);
        //繪制內(nèi)圓,也就是組成放大鏡的圓
        canvas.drawArc(mRectF, 0, 360, false, paint);
        //繪制外圓
        canvas.drawArc(mRectF2, 0, 360, false, paint);
        canvas.restore();
    }

    private void drawStartAnimView(Paint paint, Canvas canvas) {
        canvas.save();
        //根據(jù)當(dāng)前的mRectF來(lái)繪制放大鏡的把手
        canvas.drawLine(mRectF.left + cr + (cr * sign), mRectF.top + cr + (cr * sign),
                mRectF.left + cr + (2 * cr * sign), mRectF.top + cr + (2 * cr * sign), paint);
        //繪制放大鏡的圓
        canvas.drawArc(mRectF, 0, 360, false, paint);
        //繪制外圓,由于mPro是從0-1不斷增加,這里的繪制的角度就會(huì)不斷變小,
        //從而形成動(dòng)畫
        canvas.drawArc(mRectF2, 90, -360 * (1 - mPro), false, paint);
        //當(dāng)mPro 大于 0.7時(shí)開始繪制橫線,會(huì)不斷變長(zhǎng)
        if (mPro >= 0.7f) {
            canvas.drawLine((1 - mPro + 0.7f) * (mRectF2.right - 3 * cr), mRectF2.bottom,
                    (mRectF2.right - 3 * cr), mRectF2.bottom, paint);
        }
        canvas.restore();
        //tran表示平移的距離,同樣會(huì)不斷變化然后再給兩個(gè)RectF賦值
        mRectF.left = cx - cr + tran * mPro;
        mRectF.right = cx + cr + tran * mPro;
        mRectF2.left = cx - 3 * cr + tran * mPro;
        mRectF2.right = cx + 3 * cr + tran * mPro;
    }

注釋相當(dāng)清晰,這里就不再解釋了??吹搅诉@里大家應(yīng)該都已經(jīng)明白了JJSearchViewAnim具體是如何實(shí)現(xiàn)的了,而且也已經(jīng)掌握了該如何開發(fā)此類動(dòng)畫效果。但是我們學(xué)習(xí)這些動(dòng)畫最終是想要應(yīng)用到項(xiàng)目中去的,那么拿剛剛這種動(dòng)畫來(lái)說(shuō),目前在項(xiàng)目中應(yīng)該是無(wú)法使用的,那么我們?cè)趺床拍苡挚煊趾?jiǎn)單的應(yīng)用到項(xiàng)目中去呢?接下來(lái)我們就講如何簡(jiǎn)單封裝JJCircleToLineAlphaController并實(shí)際應(yīng)用到項(xiàng)目中去。

5.項(xiàng)目應(yīng)用

再拿出這張?jiān)O(shè)計(jì)圖。。。:

JJCircleToLineAlphaController

JJCircleToLineAlphaController實(shí)現(xiàn)的動(dòng)畫,看上出應(yīng)該是本身是一個(gè)圓環(huán)加一個(gè)放大鏡,當(dāng)我們點(diǎn)擊之后,然后執(zhí)行動(dòng)畫,最終形成一個(gè)白色橫線的輸入框,當(dāng)我們輸入文字之后,點(diǎn)擊搜索就應(yīng)該可以進(jìn)行搜索了。

這里我提供一個(gè)比較簡(jiǎn)單的實(shí)現(xiàn)思路:就是我們自己實(shí)現(xiàn)一個(gè)布局將EditText和這個(gè)JJSearchView疊加放置,這里要注意EditText的寬度要比JJSearchView要短,最好是一個(gè)放大鏡的寬度。首先隱藏EditText,點(diǎn)擊JJSearchView執(zhí)行動(dòng)畫,然后顯示EditText再點(diǎn)擊搜索按鈕時(shí),這時(shí)候我們通過(guò)JJSearchView的狀態(tài)來(lái)判斷是否需要搜索.這樣就簡(jiǎn)單的完成了一個(gè)帶動(dòng)畫的SearchView實(shí)現(xiàn)了.下面我們大致貼出具體的實(shí)現(xiàn),大家也可以到我fork的分支上去查看:地址在這

1.首先是布局文件


<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <com.cjj.sva.JJSearchView
        android:id="@+id/search_view"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:layout_centerInParent="true"/>

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:background="@null"
        android:layout_centerInParent="true"
        android:textCursorDrawable="@null"
        android:textColor="@android:color/white"
        android:textSize="14sp"
        android:singleLine="true"
        android:visibility="invisible"
        android:layout_marginLeft="12dp"/>

</merge>

2.CircleSearchView的具體實(shí)現(xiàn)


public class CircleSearchView extends RelativeLayout {
    private Context mContext;
    private JJSearchView mSearchView;
    private EditText mEditText;

    public CircleSearchView(Context context) {
        this(context, null);
    }

    public CircleSearchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initLayout(context);
    }

    private void initLayout(Context context) {
        this.mContext = context;
        LayoutInflater.from(mContext).inflate(R.layout.view_circle_search, this);
        mSearchView = (JJSearchView) findViewById(R.id.search_view);
        mSearchView.setController(new JJCircleToLineAlphaController());
        mEditText = (EditText) findViewById(R.id.edit_text);

        mSearchView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSearchView.getState() == JJBaseController.STATE_ANIM_NONE) {
                    mSearchView.startAnim();
                    mEditText.setVisibility(View.VISIBLE);
                    mEditText.bringToFront();
                } else if (mSearchView.getState() == JJBaseController.STATE_ANIM_START) {
                    Toast.makeText(mContext, "正在搜索", Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

主要看onClick(View v);方法中的實(shí)現(xiàn)。

2.JJCircleToLineAlphaController的修改

要想很好的實(shí)現(xiàn)平移的距離以及最終橫線的長(zhǎng)度,需要修改一些JJCircleToLineAlphaController中的方法,這里就不再貼代碼了,有需要的可以去這里查看。

6.個(gè)人評(píng)價(jià)

JJSearchViewAnim實(shí)現(xiàn)了多種酷炫的搜索動(dòng)畫,我們不僅能從項(xiàng)目里學(xué)到大量的動(dòng)畫相關(guān)的用法,更能學(xué)到如何去分解和思考一個(gè)動(dòng)畫的實(shí)現(xiàn)。是一個(gè)非常值得我們學(xué)習(xí)的項(xiàng)目。不過(guò)項(xiàng)目中可能有一些數(shù)字或者變量沒有做注釋,有可能會(huì)影響代碼的閱讀,不過(guò)CJJ同學(xué)已經(jīng)著手開始優(yōu)化了,很快就會(huì)更新。

另外關(guān)于實(shí)現(xiàn)的這些效果并沒有辦法直接在項(xiàng)目中使用的問(wèn)題,CJJ同學(xué)的初衷是想讓大家從項(xiàng)目中學(xué)習(xí)動(dòng)畫實(shí)現(xiàn)的思路與技巧,修改之后放到自己的項(xiàng)目中去而不是做一個(gè)伸手黨總想直接使用最好。在修改的過(guò)程中更能提升自己的編程能力。這點(diǎn)我很贊同CJJ(其實(shí)我們就是懶=。=)。好了今天的文章就寫到這兒,如果有什么想看的開源項(xiàng)目歡迎給我留言,如果是我目前的能力能分析好的開源項(xiàng)目我都會(huì)考慮去寫,最后謝謝大家。周末愉快_

我每周會(huì)寫一篇源代碼分析的文章,以后也可能會(huì)有其他主題.
如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)sky
地址: http://weibo.com/u/2030683111
每周我會(huì)第一時(shí)間在微博分享我寫的文章,也會(huì)積極轉(zhuǎn)發(fā)更多有用的知識(shí)給大家.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容