2 Star 0 Fork 0

BeyondYuan / InsCycleImageView

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
BSD-2-Clause

#InsCycleImageView

使用步骤:

1:在build.gradle增加依赖:

  dependencies {
    compile 'com.qintong:insLoadingAnimation:1.0.1'
  }



2:InsLoadingView继承自ImageView, 所以最基本的,可以按照ImageView的用法使用InsLoadingView:

  <com.qintong.library.InsLoadingView
      android:layout_centerInParent="true"
      android:id="@+id/loading_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/pink"/>

3:Step 3
  设置状态:

  您可以手动设置其状态,来对应在您应用中的当前状态。InsLoadingView的状态有:
  LOADING: 表示InsLoadingView被点击之后正在加载内容(未加载完毕之前),该状态下动画正在执行。
  UNCLICKED: 该InsLoadingView被点击之前的状态,此状态下动画停止。
  CLICKED: 表示InsLoadingView被点击和加载过,此状态下动画停止切圆圈的颜色为灰色。
  默认的状态是LOADING。

  可以通过一下代码设置状态:
  xml:

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

  java:

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

4:设置颜色

  设置start color和start color,InsLoadingView的圆圈会显示两个颜色间的过渡。
  可以按如下代码设置:

  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

  默认的start color和start color为#FFF700C2和#FFFFD900。

5:设置速度

  通过设置环绕动画的时间和整体旋转的时间来改变速度:

  xml:

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

  java:

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

  默认的时间为2000ms和10000ms。




  =====================================================

实现代码

2.实现

完整的代码请见https://github.com/qintong91/InsLoadingAnimation 下面就对代码进行分析。 InsLoadingView继承自ImageView,动画效果主要通过重写onDraw()函数重新绘制。所以可以先看onDraw()方法:

 @Override
 protected synchronized 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()为实现显示圆形图片重新完成了绘制图片的过程。之后根据当前status绘制图片外的圈:status为LOADING时候绘制时是动画,其他两种情况绘制是静态的圆圈。 (1) 动画绘制:

LOADING时候的动画是项目中最核心的部分。从动画效果中可以看出,圆弧的两端都在运动:运动较慢的一端其实反应了外圈的整体旋转(连同颜色),较快一端的旋转还有两个过程:圆弧向外“伸展”一圈和向回“收缩”一圈的过程。 degress和cricleWidth是实时变化的,他们的值由ValueAnimator设置,这两个值分别表示整个动画整体旋转的角度(也就是动画中转速较慢一端)和转速较快的圆弧的动画。两个变量的单位都是度degress范围为0-360,cricleWidth范围为-360到360。cricleWidth圆弧向回“收缩”和向外“伸展”的过程,分别对应代码中的a和b过程,对应的circleWidth范围为-360—0度和0—360度。 在a过程中,cricleWidth + 360换算得到成正的adjustCricleWidth,adjustCricleWidth到360度绘制一个扇形圆弧,adjustCricleWidth到0度,依次向后每隔12度画小的扇形圆弧,圆弧的宽度递减。 b过程中:从0到cricleWidth:最前端绘制4个小扇形圆弧,其后到0度绘制一个长圆弧。从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) 点击View收缩效果:

在onDraw()方法中有:

     canvas.scale(mScale, mScale, centerX(), centerY());

控制了View在点击后的整体收缩效果,mScale参数由ValueAnimator和触摸事件控制。在onTouchEvent()中我们要分析event,ACTION_DOWN时候按下mScale开始变小,从当前值向最向0.9变化(中间值由ValueAnimator生成),在ACTION_UP和ACTION_CANCEL时候手指抬起,mScale由当前值向1变化。 这里值得注意的是,在重写onTouchEvent()时候,有两点要注意:1.要保证super.onTouchEvent(event)被调用,否则该View的OnClickListener和OnLongClickListener将不会响应(具体可见事件传递机制,OnClickListener/OnLongClickListener层级最低)。2.在处理ACTION_DOWN时候要保证返回值为True,否则同次动作的ACTION_UP等事件将不会再响应,这也是事件传递机制的内容。为保证这两点,此处代码如下:

 @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:

该项目用到了三个ValueAnimator:分别控制前文的degress,cricleWidth以及mScale,绘制圆弧的过程中是减速的过程,所以用了减速插值器,其他两个过程用的都是线性插值器。此外,还需要判断当前是绘制圆弧向外伸展还是向内伸缩,所以用了个boolean值isFirstCircle进行判断,在动画Repeat时候对其值反转。代码如下:

 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设置为其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():

因为该控件是圆形,所以还需要重写onMeasure()方法,使其最后长和高一致,并针对MATCH_PARENT和WRAP_CONTENT以及指定具体宽高的情况下分别处理,注意WRAP_CONTENT下这里是指定了最大宽/高为300px,这与ImageView不同。代码如下:

 @Override
 protected synchronized 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);
 }
Copyright (c) 2017, BeyondYuan All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

简介

一个自定义的圆形图片,包含几种状态,点击状态,没有点击状态,默认状态,点击状态是圆形图片外有一个类似于进度条的圆圈在旋转,当然这个进度条的颜色是可以改变的,包括改变前的颜色和,改编后的颜色,在旋转的过程当中有一个过渡,从开始颜色逐渐过渡到结束颜色,同时,这个进度的时间也是可以控制的,详情见md文件 展开 收起
Android
BSD-2-Clause
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Android
1
https://gitee.com/BeyondYuanYuan/inscycleimageview.git
git@gitee.com:BeyondYuanYuan/inscycleimageview.git
BeyondYuanYuan
inscycleimageview
InsCycleImageView
master

搜索帮助