美文网首页程序员Android开发
BMoveView,RadioGroup添加移动的特效View(

BMoveView,RadioGroup添加移动的特效View(

作者: 不识水的鱼 | 来源:发表于2018-09-07 10:52 被阅读4次

    话说BMoveView最初只是为了实现一个底部RadioButtton的移动动画实现的效果,都是很久之前的一个自定义View了,现在重新修改了部分实现,优化了一些方法,支持更多的RadioButtton个数了,以前只支持三个,现在支持五、六、七、八、N个RadioButtton了,添加了动态实现方式。

    动态图还是以前的。O(∩_∩)O

    过时的上一篇链接 BMoveView,RadioGroup添加移动的特效View

    RadioButton 除了变颜色,添加图片显示外,我们还可以添加如下的特定效果。动画可以增加APP的美感。

    先上图:

    RadioButtton移动特效.gif

    很多属性可以自定义

    我的github 源码使用链接
    BMoveView链接
    很多的自定义View

    欢迎点个Star
    属性 含义
    circleColor 圆环的颜色
    lineColor 下面的线条的颜色
    lineDuration 线条头的移动时间(单位ms)
    lineWidth 线条的宽度
    circleDuration 圆圈的动画时间(单位ms)
    circleCenterColor 圆圈中心的颜色(可以不和背景一样)
    circlemRadio 圆圈的半径
    buttonCount button个数

    最后一个属性是新添加的 buttonCount,可以设置button的个数,也可以通过代码设置

    mBMoveView.setButonCount(4);
    

    以上是所有的属性,可以实现多个button的移动动画。使用方法还是差不多。可以查看github连接

    BMoveView github地址

    在布局文件XML里

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="match_parent"
        xmlns:yk="http://schemas.android.com/apk/res-auto"
        tools:context="com.yk.bmoveview.MainActivity">
    
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <com.yk.bmoveview.BMoveView
            android:id="@+id/bmoveview"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            yk:circleColor="#fd4040"
            yk:lineColor="#fd4040"
            yk:lineDuration="800"
            yk:lineWidth="3"
            yk:circleDuration="500"
            yk:circleCenterColor="#FFFFFF"
            yk:circlemRadio="22"
            />
        <RadioGroup
            android:id="@+id/rg_group"
            android:gravity="center"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="60dp">
            <RadioButton
                android:id="@+id/rb_rec"
                android:button="@null"
                android:text="推荐"
                android:visibility="visible"
                android:layout_weight="1"
                android:textColor="@drawable/rb_button"
                android:textSize="18sp"
                android:gravity="center"
                android:layout_width="0dp"
                android:layout_height="match_parent"/>
            <RadioButton
                android:id="@+id/rb_first"
                android:button="@null"
                android:text="索引"
                android:layout_weight="1"
                android:textColor="@drawable/rb_button"
                android:textSize="18sp"
                android:gravity="center"
                android:layout_width="0dp"
                android:layout_height="match_parent"/>
            <RadioButton
                android:id="@+id/rb_second"
                android:button="@null"
                android:text="热门"
                android:layout_weight="1"
                android:textColor="@drawable/rb_button"
                android:textSize="18sp"
                android:gravity="center"
                android:layout_width="0dp"
                android:layout_height="match_parent"/>
            <RadioButton
                android:id="@+id/rb_third"
                android:button="@null"
                android:text="我的"
                android:layout_weight="1"
                android:textColor="@drawable/rb_button"
                android:textSize="18sp"
                android:gravity="center"
                android:layout_width="0dp"
                android:layout_height="match_parent"/>
        </RadioGroup>
    </RelativeLayout>  
    

    BMoveView作为背景实现,添加RadioGroup,务必保证RadioButton的个数和我们设置的buttonCount保持一致,否者会出错。

    在Activity里,如下:

     public class MainActivity extends AppCompatActivity {
    
    private int mFirstPos;
    private int mLastPos;
    private BMoveView mBMoveView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bMoveInit();
    }
    
    private void bMoveInit() {
        mBMoveView = (BMoveView) findViewById(R.id.bmoveview);
        RadioGroup mRadioGroup= (RadioGroup) findViewById(R.id.rg_group);
        ((RadioButton) (mRadioGroup.getChildAt(0))).setChecked(true);
        mFirstPos = 0;
        mBMoveView.setButonCount(4);
        mBMoveView.startAnim();
        mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                for (int i = 0; i < group.getChildCount(); i++) {
                    boolean checked = ((RadioButton) (group.getChildAt(i))).isChecked();
                    if(checked){
                        mLastPos = i;
                        mBMoveView.setTwoPos(mFirstPos, mLastPos);
                        mFirstPos = mLastPos;
                    }
                }
            }
        });
    }
    

    }

    主要是记录两次的位置,就能实现这个效果了,使用起来并不是很复杂。其中关键方法为

    mBMoveView.setTwoPos(mFirstPos, mLastPos);
    

    设置两次的位置,默认第一次设为0,即表示第一个位置在第一位,也是默认的选中第一个。

    使用方法讲完了,下面介绍是如何实现的

    主要是在onDraw里,绘制我们的view,分析动画和过程

    • 一个圆圈的动画,就是旋转
    • 下面一个线条,添加移动效果
    • 线条移动头和尾的移动时间不同
    • 移动的方向和位置

    主要由以上四步骤实现,弧,移动的线条,位置,方向

        /**
       * Created by yukun on 18-11-12.
       */
      public class BMoveView extends View {
    
      private int mWidth;
      private int mHeight;
      private Paint mPaint;
      private Paint mPaintLine;
      private RectF mRectF;
      private int mBoardWidth=50;
      private int firstPos;  //第一次点击位置
      private int mRoationx=0;
      private int mRadio=5;
      private int position=0;//点击到的button位置
      private int mLineEndLength;
      private int mLineLength;
      private int mCircleColor;
      private int mLineColor;
      private int mLineDuration;
      private int mLineWidth;
      private int mCircleDuration;
      private int mCircleCenterColor;
      private int mCirclemRadio;
      private int mButonCount;
    
      public BMoveView(Context context) {
        super(context);
        init(context,null,0);
      }
    
      public BMoveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs,0);
      }
    
      public BMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs,defStyleAttr);
      }
    
      private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BMoveView, defStyleAttr, 0);
    
        int n = a.getIndexCount();
    
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.BMoveView_circleColor:
                    mCircleColor = a.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.BMoveView_lineColor:
                    mLineColor = a.getColor(attr, Color.GRAY);
                    break;
                case R.styleable.BMoveView_circleCenterColor:
                    mCircleCenterColor = a.getColor(attr, Color.GRAY);
                    break;
                case R.styleable.BMoveView_lineDuration:
                    mLineDuration = a.getInt(attr,500);
                    break;
                case R.styleable.BMoveView_lineWidth:
                    mLineWidth = a.getInt(attr, 5);
                    break;
                case R.styleable.BMoveView_circleDuration:
                    mCircleDuration = a.getInt(attr,500);
                    break;
                case R.styleable.BMoveView_circlemRadio:
                    mCirclemRadio = a.getInt(attr,500);
                    break;
                case R.styleable.BMoveView_buttonCount:
                    mButonCount = a.getInt(attr,3);
                    break;
            }
        }
        a.recycle();
        mBoardWidth=dip2px(context,mCirclemRadio);
        mRadio=dip2px(context,mLineWidth);
        mPaint=new Paint();
        mPaintLine = new Paint();
      }
    
        /**
       * 初始化第一次的位置
       * @param firstPos
       * @param lastPos
       */
      public void setTwoPos(int firstPos,int lastPos) {
        this.firstPos = firstPos;
        this.position=lastPos;
        this.mRoationx = 0;
        //动画的方法 (lastPos-firstPos)两次相减得到需要移动的距离
        leftToRigth(lastPos - firstPos);
      }
      /**
       * button个数
       * @param butonCount
       */
    
      public void setButonCount(int butonCount) {
        mButonCount = butonCount;
      }
    
      /**
       *
       * @param startLineLastPosition 正为向右,负为想左,如果是1.则跨度为一,如果是2,则跨度为2;
       */
      private void leftToRigth(int startLineLastPosition) {
        startAnim();
        startLineAnim(startLineLastPosition);
        startLineEndAnim(startLineLastPosition);
      }
    
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
      }
    
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      }
    
      @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画弧度
        mPaint.setColor(mCircleColor);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mRadio);
        mPaint.setStyle(Paint.Style.STROKE);//只有边
        //画圆弧的矩形位置
        mRectF=new RectF(mWidth/(mButonCount*2)-mBoardWidth+position*mWidth/mButonCount,mHeight/2-mBoardWidth,mWidth/(mButonCount*2)+mBoardWidth+position*mWidth/mButonCount,mHeight/2+mBoardWidth);
        canvas.drawArc(mRectF,90,mRoationx,false,mPaint);
        //画圆覆盖
        mPaintLine.setColor(Color.BLUE);
        mPaintLine.setAntiAlias(true);
        mPaintLine.setStyle(Paint.Style.FILL);
        //可以画内圆圈的颜色
      //        canvas.drawArc(mRectF,90,mRoationx,true,mPaintLine);
        //画线条
        mPaintLine.setColor(mLineColor);
        mPaintLine.setStrokeWidth(mRadio);
        //起始和结束不同,每次动画结束位置是相同的,控制起始点和结束点
        canvas.drawLine(mWidth/(mButonCount*2)+firstPos*mWidth/mButonCount+mLineEndLength,mHeight/2+mBoardWidth,mWidth/(mButonCount*2)+firstPos*mWidth/mButonCount+mLineLength,mHeight/2+mBoardWidth, mPaintLine);
      }
    
      //圆圈的动画
      public void startAnim(){
        ValueAnimator animator = ValueAnimator.ofInt(0,360);
        animator.setDuration(mCircleDuration);
        animator.setStartDelay(mCircleDuration);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRoationx = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
      }
    
      //线条开始的动画
      private void startLineAnim(int startLineLastPosition){
        ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/mButonCount)*startLineLastPosition);
        animator.setDuration(mLineDuration);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLineLength = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
      }
    
      //线条结束的动画
      private void startLineEndAnim(int startLineLastPosition){
        ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/mButonCount)*startLineLastPosition);
        animator.setDuration(mCircleDuration);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLineEndLength = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
      }
    
      private static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
       }
      }
    

    主要代码如下

    /**
     * @param startLineLastPosition 正为向右,负为想左,如果是1.则跨度为一,如果是2,则跨度为2;
     */
    private void leftToRigth(int startLineLastPosition) {
        startAnim();
        startLineAnim(startLineLastPosition);
        startLineEndAnim(startLineLastPosition);
    }
    

    调用的方法

    主要是传递过来的startLineLastPosition

    //动画的方法 (lastPos-firstPos)两次相减得到需要移动的距离
     leftToRigth(lastPos - firstPos);
    

    其中lastPos - firstPos可以得到我们需要移动的跨度是一个button还是两个button的距离,其正负数表示我们的方向,这里对以前的方法做了简化。具体的可以看动画实现。如下:

        //圆圈的动画
     public void startAnim(){
     ValueAnimator animator = ValueAnimator.ofInt(0,360);
    animator.setDuration(mCircleDuration);
    animator.setStartDelay(mCircleDuration);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mRoationx = (int)animation.getAnimatedValue();
            postInvalidate();
        }
    });
    animator.start();
    }
    
    //线条开始的动画
    private void startLineAnim(int startLineLastPosition){
    ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/mButonCount)*startLineLastPosition);
    animator.setDuration(mLineDuration);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mLineLength = (int)animation.getAnimatedValue();
            postInvalidate();
        }
    });
    animator.start();
    }
    
    //线条结束的动画
    private void startLineEndAnim(int startLineLastPosition){
    ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/mButonCount)*startLineLastPosition);
    animator.setDuration(mCircleDuration);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mLineEndLength = (int)animation.getAnimatedValue();
            postInvalidate();
        }
    });
    animator.start();
    }
    

    对于弧度的动画,我们为你记录了弧度由0~360的弧度,通过delay延迟得到画弧度的实现,对于移动的线条,其实很容易就得到了最后的位置,其中也记录了之前的位置,为了实现动画,通过两个线条的参数,通过ValueAnimator动画得到不同的延迟,显示线条移动的轨迹。

    下面是onDraw的实现,

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //画弧度
    mPaint.setColor(mCircleColor);
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(mRadio);
    mPaint.setStyle(Paint.Style.STROKE);//只有边
    //画圆弧的矩形位置
    mRectF=new RectF(mWidth/(mButonCount*2)-mBoardWidth+position*mWidth/mButonCount,mHeight/2-mBoardWidth,mWidth/(mButonCount*2)+mBoardWidth+position*mWidth/mButonCount,mHeight/2+mBoardWidth);
    canvas.drawArc(mRectF,90,mRoationx,false,mPaint);
    //画圆覆盖
     // mPaintLine.setColor(Color.BLUE);
      mPaintLine.setAntiAlias(true);
     mPaintLine.setStyle(Paint.Style.FILL);
    //可以画内圆圈的颜色
      //        canvas.drawArc(mRectF,90,mRoationx,true,mPaintLine);
      //画线条
    mPaintLine.setColor(mLineColor);
    mPaintLine.setStrokeWidth(mRadio);
    //起始和结束不同,每次动画结束位置是相同的,控制起始点和结束点
    canvas.drawLine(mWidth/(mButonCount*2)+firstPos*mWidth/mButonCount+mLineEndLength,mHeight/2+mBoardWidth,mWidth/(mButonCount*2)+firstPos*mWidth/mButonCount+mLineLength,mHeight/2+mBoardWidth, mPaintLine);
    }
    

    几个变量控制,需要理解清除,一个是弧度的mRoationx,这个由0~360度的变化。mLineEndLengthmLineLength,分别是线条移动的时间差,得到移动的动画效果。通过delay的连接,使它们连接起来,形成完整的动画效果。

    ------------------------- 特别提醒 -------------------------

    解开注释

     //画圆覆盖
      mPaintLine.setColor(Color.BLUE);
      mPaintLine.setAntiAlias(true);
     mPaintLine.setStyle(Paint.Style.FILL);
    //可以画内圆圈的颜色
     canvas.drawArc(mRectF,90,mRoationx,true,mPaintLine);
    

    可以得到一些异形效果呃。😄🤭

    S81112-16581323.jpg S81112-16580879.jpg S81112-16580527.jpg

    gif图太小,找点大图镇楼,看看完整的效果

    S70427-19180549.jpg S81112-15260812.jpg S81112-15280658.jpg S81112-15275978.jpg

    相关文章

      网友评论

        本文标题:BMoveView,RadioGroup添加移动的特效View(

        本文链接:https://www.haomeiwen.com/subject/ianqgftx.html