<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android仿instagram頭像點(diǎn)擊加載動畫

          共 12000字,需瀏覽 24分鐘

           ·

          2021-12-31 11:51

          前段時(shí)間發(fā)現(xiàn)instagram點(diǎn)擊用戶頭像的加載小視頻動畫,效果如下:


          對,就是轉(zhuǎn)圈圈的這個(gè),這么酷炫,我也要做一個(gè)!在整理代碼和總結(jié)時(shí)候,神奇的事情發(fā)生了,在我日常刷微博的時(shí)候點(diǎn)開微博客戶端時(shí)候突然發(fā)現(xiàn):


          緣分啊,發(fā)現(xiàn)了微博Android客戶端也上線了類似動畫!等等,不是類似,這是特么是除了顏色和ins的一毛一樣啊!

          既然這個(gè)動畫效果這么火,那還不趕快把我實(shí)現(xiàn)分享出來


          如下就是實(shí)現(xiàn)的效果:


          1、介紹


          InsLoadingView繼承自ImageView,其對應(yīng)的image顯示為圓形。

          InsLoadingView有三種狀態(tài):LOADING/UNCLICKED/CLICKED,Loading時(shí)候輪廓有不斷循環(huán)的動畫,如上圖(下文分析源碼時(shí)候會詳細(xì)闡明其過程)。

          UNCLICKED時(shí)外側(cè)輪廓為靜態(tài)的彩色圈,CLICKED外層為靜態(tài)的灰色圈。此外,在其被點(diǎn)擊時(shí)還有控件收縮的動畫效果。

          注意:由于狀態(tài)是與應(yīng)用中的情況相關(guān)的,所以狀態(tài)變化需要用戶手動去設(shè)置。

          整體效果如下(感謝家里的喵主子~)


          2、使用


          如果你想在自己的項(xiàng)目中使用的話,可以按如下幾步進(jìn)行:


          Step 1


          在build.gradle增加依賴:
          dependencies {  compile 'com.qintong:insLoadingAnimation:1.0.1'}

          Step 2


          InsLoadingView繼承自ImageView, 所以最基本的,可以按照ImageView的用法使用InsLoadingView:
              android:layout_centerInParent="true"    android:id="@+id/loading_view"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:src="@mipmap/pink"/>

          Step 3


          設(shè)置狀態(tài):


          您可以手動設(shè)置其狀態(tài),來對應(yīng)在您應(yīng)用中的當(dāng)前狀態(tài)。

          InsLoadingView的狀態(tài)有:

          LOADING: 表示InsLoadingView被點(diǎn)擊之后正在加載內(nèi)容(未加載完畢之前),該狀態(tài)下動畫正在執(zhí)行。

          UNCLICKED: 該InsLoadingView被點(diǎn)擊之前的狀態(tài),此狀態(tài)下動畫停止。

          CLICKED: 表示InsLoadingView被點(diǎn)擊和加載過,此狀態(tài)下動畫停止切圓圈的顏色為灰色。

          默認(rèn)的狀態(tài)是LOADING。

          可以通過一下代碼設(shè)置狀態(tài):

          xml:
            app:status="loading" //or "clicked",or "clicked"

          java:
            mInsLoadingView.setStatus(InsLoadingView.Status.LOADING); //Or InsLoadingView.Status.CLICKED, InsLoadingView.Status.UNCLICKED

          設(shè)置顏色


          設(shè)置start color和start color,InsLoadingView的圓圈會顯示兩個(gè)顏色間的過渡。

          可以按如下代碼設(shè)置:

          xml:
            app:start_color="#FFF700C2" //or your color  app:end_color="#FFFFD900" //or your color

          java:
            mInsLoadingView.setStartColor(Color.YELLOW); //or your color  mInsLoadingView.setEndColor(Color.BLUE); //or your color

          默認(rèn)的start color和start color為#FFF700C2和#FFFFD900。


          設(shè)置速度


          通過設(shè)置環(huán)繞動畫的時(shí)間和整體旋轉(zhuǎn)的時(shí)間來改變速度:

          xml:
            app:circle_duration="2000"  app:rotate_duration="10000"

          java:
            mInsLoadingView.setCircleDuration(2000);  mInsLoadingView.setRotateDuration(10000);

          默認(rèn)的時(shí)間為2000ms和10000ms。

          2、實(shí)現(xiàn)


          下面就對代碼進(jìn)行分析。

          InsLoadingView繼承自ImageView,動畫效果主要通過重寫onDraw()函數(shù)重新繪制。所以可以先看onDraw()方法:
              @Override    protected void onDraw(Canvas canvas) {        canvas.scale(mScale, mScale, centerX(), centerY());        drawBitmap(canvas);        Paint paint = getPaint(getColor(0), getColor(360), 360);        switch (mStatus) {            case LOADING:                drawTrack(canvas, paint);                break;            case UNCLICKED:                drawCircle(canvas, paint);                break;            case CLICKED:                drawClickedircle(canvas);                break;        }    }

          drawBitmap()為實(shí)現(xiàn)顯示圓形圖片重新完成了繪制圖片的過程。之后根據(jù)當(dāng)前status繪制圖片外的圈:status為LOADING時(shí)候繪制時(shí)是動畫,其他兩種情況繪制是靜態(tài)的圓圈。

          (1) 動畫繪制:


          LOADING時(shí)候的動畫是項(xiàng)目中最核心的部分。從動畫效果中可以看出,圓弧的兩端都在運(yùn)動:運(yùn)動較慢的一端其實(shí)反應(yīng)了外圈的整體旋轉(zhuǎn)(連同顏色),較快一端的旋轉(zhuǎn)還有兩個(gè)過程:圓弧向外“伸展”一圈和向回“收縮”一圈的過程。

          degress和cricleWidth是實(shí)時(shí)變化的,他們的值由ValueAnimator設(shè)置,這兩個(gè)值分別表示整個(gè)動畫整體旋轉(zhuǎn)的角度(也就是動畫中轉(zhuǎn)速較慢一端)和轉(zhuǎn)速較快的圓弧的動畫。兩個(gè)變量的單位都是度degress范圍為0-360,cricleWidth范圍為-360到360。cricleWidth圓弧向回“收縮”和向外“伸展”的過程,分別對應(yīng)代碼中的a和b過程,對應(yīng)的circleWidth范圍為-360—0度和0—360度。

          在a過程中,cricleWidth + 360換算得到成正的adjustCricleWidth,adjustCricleWidth到360度繪制一個(gè)扇形圓弧,adjustCricleWidth到0度,依次向后每隔12度畫小的扇形圓弧,圓弧的寬度遞減。

          b過程中:從0到cricleWidth:最前端繪制4個(gè)小扇形圓弧,其后到0度繪制一個(gè)長圓弧。從360度到cricleWidth,每間隔12度依次繪制小圓弧,其寬度遞減。
              private void drawTrack(Canvas canvas, Paint paint) {        canvas.rotate(degress, centerX(), centerY());        canvas.rotate(ARC_WIDTH, centerX(), centerY());        RectF rectF = new RectF(getWidth() * (1 - circleDia), getWidth() * (1 - circleDia),                getWidth() * circleDia, getHeight() * circleDia);        if (DEBUG) {            Log.d(TAG, "cricleWidth:" + cricleWidth);        }        if (cricleWidth < 0) {            //a            float startArg = cricleWidth + 360;            canvas.drawArc(rectF, startArg, 360 - startArg, false, paint);            float adjustCricleWidth = cricleWidth + 360;            float width = 8;            while (adjustCricleWidth > ARC_WIDTH) {                width = width - arcChangeAngle;                adjustCricleWidth = adjustCricleWidth - ARC_WIDTH;                canvas.drawArc(rectF, adjustCricleWidth, width, false, paint);            }        } else {            //b            for (int i = 0; i <= 4; i++) {                if (ARC_WIDTH * i > cricleWidth) {                    break;                }                canvas.drawArc(rectF, cricleWidth - ARC_WIDTH * i, 8 + i, false, paint);            }            if (cricleWidth > ARC_WIDTH * 4) {                canvas.drawArc(rectF, 0, cricleWidth - ARC_WIDTH * 4, false, paint);            }            float adjustCricleWidth = 360;            float width = 8 * (360 - cricleWidth) / 360;            if (DEBUG) {                Log.d(TAG, "width:" + width);            }            while (width > 0 && adjustCricleWidth > ARC_WIDTH) {                width = width - arcChangeAngle;                adjustCricleWidth = adjustCricleWidth - ARC_WIDTH;                canvas.drawArc(rectF, adjustCricleWidth, width, false, paint);            }        }    }

          (2) 點(diǎn)擊View收縮效果:


          在onDraw()方法中有:
          canvas.scale(mScale, mScale, centerX(), centerY());

          控制了View在點(diǎn)擊后的整體收縮效果,mScale參數(shù)由ValueAnimator和觸摸事件控制。在onTouchEvent()中我們要分析event,ACTION_DOWN時(shí)候按下mScale開始變小,從當(dāng)前值向最向0.9變化(中間值由ValueAnimator生成),在ACTION_UP和ACTION_CANCEL時(shí)候手指抬起,mScale由當(dāng)前值向1變化。

          這里值得注意的是,在重寫onTouchEvent()時(shí)候,有兩點(diǎn)要注意:

          1、要保證super.onTouchEvent(event)被調(diào)用,否則該View的OnClickListener和OnLongClickListener將不會響應(yīng)(具體可見事件傳遞機(jī)制,OnClickListener/OnLongClickListener層級最低)。

          2、在處理ACTION_DOWN時(shí)候要保證返回值為True,否則同次動作的ACTION_UP等事件將不會再響應(yīng),這也是事件傳遞機(jī)制的內(nèi)容。為保證這兩點(diǎn),此處代碼如下:
              @Override    public boolean onTouchEvent(MotionEvent event) {        boolean result = false;        if (DEBUG) {            Log.d(TAG, "onTouchEvent: " + event.getAction());        }        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN: {                startDownAnim();                result = true;                break;            }            case MotionEvent.ACTION_UP: {                startUpAnim();                break;            }            case MotionEvent.ACTION_CANCEL: {                startUpAnim();                break;            }        }        return super.onTouchEvent(event) || result;    }
          private void startDownAnim() { mTouchAnim.setFloatValues(mScale, 0.9f); mTouchAnim.start();
          }
          private void startUpAnim() { mTouchAnim.setFloatValues(mScale, 1); mTouchAnim.start(); }

          (3) ValueAnimator:


          該項(xiàng)目用到了三個(gè)ValueAnimator:分別控制前文的degress,cricleWidth以及mScale,繪制圓弧的過程中是減速的過程,所以用了減速插值器,其他兩個(gè)過程用的都是線性插值器。此外,還需要判斷當(dāng)前是繪制圓弧向外伸展還是向內(nèi)伸縮,所以用了個(gè)boolean值isFirstCircle進(jìn)行判斷,在動畫Repeat時(shí)候?qū)ζ渲捣崔D(zhuǎn)。代碼如下:
              private void onCreateAnimators() {        mRotateAnim = ValueAnimator.ofFloat(0, 180, 360);        mRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                degress = (float) animation.getAnimatedValue();                postInvalidate();            }        });        mRotateAnim.setInterpolator(new LinearInterpolator());        mRotateAnim.setDuration(mRotateDuration);        mRotateAnim.setRepeatCount(-1);        mCircleAnim = ValueAnimator.ofFloat(0, 360);        mCircleAnim.setInterpolator(new DecelerateInterpolator());        mCircleAnim.setDuration(mCircleDuration);        mCircleAnim.setRepeatCount(-1);        mCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                if (isFirstCircle) {                    cricleWidth = (float) animation.getAnimatedValue();                } else {                    cricleWidth = (float) animation.getAnimatedValue() - 360;                }                postInvalidate();            }        });        mCircleAnim.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animation) {
          }
          @Override public void onAnimationEnd(Animator animation) {
          }
          @Override public void onAnimationCancel(Animator animation) {
          }
          @Override public void onAnimationRepeat(Animator animation) { isFirstCircle = !isFirstCircle; } }); mTouchAnim = new ValueAnimator(); mTouchAnim.setInterpolator(new DecelerateInterpolator()); mTouchAnim.setDuration(200); mTouchAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mScale = (float) animation.getAnimatedValue(); postInvalidate(); } }); startAnim(); }

          (4) 繪制圓形圖片:


          由于與ImageView不同,這里圖片要顯示成圓形,所以這里我們通過Drawble拿到Bitmap對象后,將其BitmapShader修剪成正方形,paint的shader設(shè)置為其BitmapShader,再用該paint畫圓:
              private void drawBitmap(Canvas canvas) {        Paint bitmapPaint = new Paint();        setBitmapShader(bitmapPaint);        RectF rectF = new RectF(getWidth() * (1 - bitmapDia), getWidth() * (1 - bitmapDia),                getWidth() * bitmapDia, getHeight() * bitmapDia);        canvas.drawOval(rectF, bitmapPaint);    }
          private void setBitmapShader(Paint paint) { Drawable drawable = getDrawable(); Matrix matrix = new Matrix(); if (null == drawable) { return; } Bitmap bitmap = drawableToBitmap(drawable); BitmapShader tshader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); float scale = 1.0f; int bSize = Math.min(bitmap.getWidth(), bitmap.getHeight()); scale = getWidth() * 1.0f / bSize; matrix.setScale(scale, scale); if (bitmap.getWidth() > bitmap.getHeight()) { matrix.postTranslate(-(bitmap.getWidth() * scale - getWidth()) / 2, 0); } else { matrix.postTranslate(0, -(bitmap.getHeight() * scale - getHeight()) / 2); } tshader.setLocalMatrix(matrix); paint.setShader(tshader); }
          private Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; return bitmapDrawable.getBitmap(); } int w = drawable.getIntrinsicWidth(); int h = drawable.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); drawable.draw(canvas); return bitmap; }

          (5) 顏色:


          在onDraw()中,getPaint()得到了從mStartColor到mEndColor的過渡的顏色:
             Paint paint = getPaint(mStartColor, mEndColor, 360);

          其中:
              private Paint getPaint(int startColor, int endColor, double arcWidth) {        Paint paint = new Paint();        Shader shader = new LinearGradient(0f, 0f, (float) (getWidth() * circleDia * (arcWidth - ARC_WIDTH * 4) / 360),                getHeight() * strokeWidth, startColor, endColor, CLAMP);        paint.setShader(shader);        setPaintStroke(paint);        return paint;    }

          (6) 重寫onMeasure():


          因?yàn)樵摽丶菆A形,所以還需要重寫onMeasure()方法,使其最后長和高一致,并針對MATCH_PARENT和WRAP_CONTENT以及指定具體寬高的情況下分別處理,注意WRAP_CONTENT下這里是指定了最大寬/高為300px,這與ImageView不同。代碼如下:
              @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);        final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);        if (DEBUG) {            Log.d(TAG, "onMeasure widthMeasureSpec:" + widthSpecMode + "--" + widthSpecSize);            Log.d(TAG, "onMeasure heightMeasureSpec:" + heightSpecMode + "--" + heightSpecSize);        }        int width;        if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {            width = Math.min(widthSpecSize, heightSpecSize);        } else {            width = Math.min(widthSpecSize, heightSpecSize);            width = Math.min(width, 300);        }        setMeasuredDimension(width, width);    }

          總結(jié)


          InsLoadingAnimation主要是由屬性動畫實(shí)現(xiàn),也加深了對View的生命周期和事件傳遞等方法的理解。更進(jìn)一步的,也練習(xí)了canvas上的繪圖。最后我們就有了和Instagram和微博一樣炫酷的動畫效果~

          源碼地址:
          https://github.com/qintong91/InsLoadingAnimation

          到這里就結(jié)束啦。
          瀏覽 119
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线观看 禁无码精品软件小说 | 艹逼网站 | 快一本码道在线播放视频国产 | 成人做爰黄A片免费看直播室男男 | 搞逼逼 |