google也曾推荐使用底部导航栏的方式进行切换,使用RadioButton
可完成切换的动作
RadioButton
除了变颜色,添加图片显示外,我们还可以添加如下的特定效果.动画可以增加APP的美感,提升用户体验度
先上图:
1493335479979.mp4_1493336353.gif很多属性可以自定义
我的github 源码使用链接
BMoveView链接
很多的自定义View
欢迎点个Star
属性 | 含义 |
---|---|
circleColor | 圆环的颜色 |
lineColor | 下面的线条的颜色 |
lineDuration | 线条头的移动时间(单位ms) |
lineWidth | 线条的宽度 |
circleDuration | 圆圈的动画时间(单位ms) |
circleCenterColor | 圆圈中心的颜色(可以不和背景一样) |
circleRadio | 圆圈的半径 |
以上就是所有的属性
可以设置不同的移动效果,根据个人需求来实现
使用
在布局文件XML里
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<com.yk.myselfview.views.BMoveView
android:id="@+id/bmoveview"
android:layout_width="match_parent"
android:layout_height="60dp"
yk:circleColor="#fd4040"
yk:lineColor="#fd4040"
yk:lineDuration="150"
yk:lineWidth="3"
yk:circleDuration="500"
yk:circleCenterColor="#FFFFFF"
yk:circleRadio="25"
/>
<RadioGroup
android:id="@+id/rg_group"
android:gravity="center"
android:weightSum="3"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<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>
其中
<com.yk.myselfview.views.BMoveView
android:id="@+id/bmoveview"
android:layout_width="match_parent"
android:layout_height="60dp"
yk:circleColor="#fd4040"
yk:lineColor="#fd4040"
yk:lineDuration="150"
yk:lineWidth="3"
yk:circleDuration="500"
yk:circleCenterColor="#FFFFFF"
yk:circleRadio="25"/>
为主要的布局,是我们的自定义的BMoveView
在Activity里,如下:
public class BMoveViewActivity extends Activity {
private int mFirstPos; //上一次的radiobutton位置
private int mLastPos; //点击的radiobutton位置
private BMoveView mBMoveView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bmove_view);
bMoveInit();
}
private void bMoveInit() {
mBMoveView = (BMoveView) findViewById(R.id.bmoveview);
RadioGroup radioGroup= (RadioGroup) findViewById(R.id.rg_group);
((RadioButton) (radioGroup.getChildAt(0))).setChecked(true);
mFirstPos = 0;
mBMoveView.startAnim();
radioGroup.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; //记录上一次的点击位置,更新位置
}
}
}
});
}
}
是不是很简单,主要是记录两次的位置,就能实现这个效果了
使用方法讲完了,下面介绍是如何实现的
主要是在onDraw里,绘制我们的view,分析动画和过程
- 一个圆圈的动画,就是旋转
- 下面一个线条,添加移动效果
- 线条移动头和尾的移动时间不同
- 移动的方向和位置
主要代码如下
public class BMoveView extends View {
private int mWidth;
private int mHeight;
private Paint mPaint=new Paint();
private RectF mRectF;
private int boardWidth=50;
private int maxCircle=360;
private int firstPos; //第一次点击位置
private int lastPos; //第二次点击位置
private int lineControl; //下面控制线条的起始位置
private int mRoationx=0;
private int radio=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 mCircleRadio;
public void setTwoPos(int firstPos,int lastPos) {
this.firstPos = firstPos;
this.lastPos=lastPos;
devidePos();
}
//判断两次的位置,选择不同动画
private void devidePos() {
setRoationx(0);
if(firstPos==0){
if(lastPos==1){
leftToRigth(lastPos, 1);
} else if(lastPos==2){
leftToRigth(lastPos, 2);
}
}else if(firstPos==1){
if(lastPos==0){
leftToRigth(lastPos, -1);
} else if(lastPos==2){
leftToRigth(lastPos, 1);
}
}
else if(firstPos==2){
if(lastPos==0){
leftToRigth(lastPos, -2);
} else if(lastPos==1){
leftToRigth(lastPos, -1);
}
}
}
/**
*
* @param lastPos 上一次的位置
* @param startLineineLastPosition 正为向右,负为想左,如果是1.则跨度为一,如果是2,则跨度为2;
*/
private void leftToRigth(int lastPos, int startLineineLastPosition) {
setPosition(lastPos);
startAnim();
lineControl=lastPos-startLineineLastPosition;//下面控制线条的起始位置
startLineAnim(startLineineLastPosition);
startLineEndAnim(startLineineLastPosition);
}
public void setRoationx(int roationx) {
mRoationx = roationx;
}
public void setPosition(int position) {
this.position = position;
}
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_circleRadio:
mCircleRadio = a.getInt(attr,500);
break;
}
}
a.recycle();
boardWidth=dip2px(context,mCircleRadio);
radio=dip2px(context,mLineWidth);
}
@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.setStyle(Paint.Style.FILL);
//位置计算比较麻烦,用比例
mRectF=new RectF(mWidth/6-boardWidth+position*mWidth/3,mHeight/2-boardWidth,mWidth/6+boardWidth+position*mWidth/3,mHeight/2+boardWidth);
canvas.drawArc(mRectF,90,mRoationx,true,mPaint);
//画圆覆盖
mPaint.reset();
mPaint.setColor(mCircleCenterColor);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mWidth/6+(position)*mWidth/3,mHeight/2,boardWidth-radio,mPaint);
//画线条
mPaint.setColor(mLineColor);
mPaint.setStrokeWidth(radio);
//起始和结束不同,每次动画结束位置是相同的,控制起始点和结束点
canvas.drawLine(mWidth/6+lineControl*mWidth/3+mLineEndLength,mHeight/2+boardWidth-radio/2,mWidth/6+lineControl*mWidth/3+mLineLength,mHeight/2+boardWidth-radio/2, mPaint);
}
//圆圈的动画
public void startAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,maxCircle);
animator.setDuration(mCircleDuration);
animator.setStartDelay(mLineDuration);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRoationx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
//线条开始的动画
public void startLineAnim(int startLineineLastPosition){
ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/3)*startLineineLastPosition);
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();
}
//线条结束的动画
public void startLineEndAnim(int endLineineLastPosition){
ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/3)*endLineineLastPosition);
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();
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
主要是在onDraw里的绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画弧度
mPaint.setColor(mCircleColor);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
//位置计算比较麻烦,用比例
mRectF=new RectF(mWidth/6-boardWidth+position*mWidth/3,mHeight/2-boardWidth,mWidth/6+boardWidth+position*mWidth/3,mHeight/2+boardWidth);
canvas.drawArc(mRectF,90,mRoationx,true,mPaint);
//画圆覆盖
mPaint.reset();
mPaint.setColor(mCircleCenterColor);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mWidth/6+(position)*mWidth/3,mHeight/2,boardWidth-radio,mPaint);
//画线条
mPaint.setColor(mLineColor);
mPaint.setStrokeWidth(radio);
//起始和结束不同,每次动画结束位置是相同的,控制起始点和结束点
canvas.drawLine(mWidth/6+lineControl*mWidth/3+mLineEndLength,mHeight/2+boardWidth-radio/2,mWidth/6+lineControl*mWidth/3+mLineLength,mHeight/2+boardWidth-radio/2, mPaint);
}
如上所示,选好了点的位置就是添加动画,一共写了三个动画,圆圈的动画,线条头的动画,线条尾的动画
//圆圈的动画
public void startAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,maxCircle);
animator.setDuration(mCircleDuration);
animator.setStartDelay(mLineDuration);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRoationx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
//线条开始的动画
public void startLineAnim(int startLineineLastPosition){
ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/3)*startLineineLastPosition);
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();
}
//线条结束的动画
public void startLineEndAnim(int endLineineLastPosition){
ValueAnimator animator = ValueAnimator.ofInt(0,(mWidth/3)*endLineineLastPosition);
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();
}
逻辑判断,通过两次的位置,来判断该使用哪一个动画,同时配合Radiobutton
完成移动的效果
//判断两次的位置,选择不同动画
private void devidePos() {
setRoationx(0);
if(firstPos==0){
if(lastPos==1){
leftToRigth(lastPos, 1);
} else if(lastPos==2){
leftToRigth(lastPos, 2);
}
}else if(firstPos==1){
if(lastPos==0){
leftToRigth(lastPos, -1);
} else if(lastPos==2){
leftToRigth(lastPos, 1);
}
}
else if(firstPos==2){
if(lastPos==0){
leftToRigth(lastPos, -2);
} else if(lastPos==1){
leftToRigth(lastPos, -1);
}
}
}
/**
*
* @param lastPos 上一次的位置
* @param startLineineLastPosition 正为向右,负为想左,如果是1.则跨度为一,如果是2,则跨度为2;
*/
private void leftToRigth(int lastPos, int startLineineLastPosition) {
setPosition(lastPos);
startAnim();
lineControl=lastPos-startLineineLastPosition;//下面控制线条的起始位置
startLineAnim(startLineineLastPosition);
startLineEndAnim(startLineineLastPosition);
}
再绘制view的时候,主要也使用了常用的一些方法,加上动画组合成的view
gif图太小,找点大图镇楼
S70427-19180549.jpgS70427-19175462.jpg
S70427-19170761.jpg
网友评论