您的位置:首页 > 移动开发 > Android开发

Android:自定义View实现随滑动由箭头变对勾的指示按钮

2015-07-20 23:31 866 查看
首先来看下效果^_^



实现原理并不复杂,这里我们通过自定义的方式通过位移偏差重绘线段的坐标使之有机连接起来,话不多说,代码走你┏ (゜ω゜)=☞

public class MagicButton extends View {

// 八个点,用于绘制线段
private PointF mPoint0;
private PointF mPoint1;
private PointF mPoint2;
private PointF mPoint3;
private PointF mPoint4;
private PointF mPoint5;
private PointF mPoint6;
private PointF mPoint7;

// 箭头到对勾对应的四个点
private PointF mArrawToTickPoint1, mArrawToTickPoint2, mArrawToTickPoint3, mArrawToTickPoint4;

// 对勾到箭头对应的四个点
private PointF mTickToArrawPoint1, mTickToArrawPoint2, mTickToArrawPoint3, mTickToArrawPoint4;

// 角度,对应于位移偏差,用于动态改变线段的落点
private float mProgress = 0;

// view的宽高
private int mViewWidth;
private int mViewHeight;

// view的中心
private int mViewCenterX;
private int mViewCenterY;

// 半径
private int mRadius;

// 是否绘制箭头标志位
private boolean mIsDrawArrow = true;

/**
* 画实心圆
*/
private Paint mCirclePaint;

/**
* 画符号
*/
private Paint mSymbolPaint;

public MagicButton(Context context) {
super(context);
}

public MagicButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
// 画背景圆
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(Color.parseColor("#ccaaaaaa"));

// 画符号
mSymbolPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSymbolPaint.setColor(getResources().getColor(android.R.color.white));
mSymbolPaint.setStyle(Paint.Style.STROKE);
mSymbolPaint.setStrokeCap(Paint.Cap.ROUND);
mSymbolPaint.setStrokeJoin(Paint.Join.ROUND);
}

public MagicButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MagicButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mViewWidth = measureWidth(widthMeasureSpec);
mViewHeight = measureHeight(heightMeasureSpec);
mViewCenterX = mViewWidth / 2;
mViewCenterY = mViewHeight / 2;
mRadius = mViewCenterX <= mViewCenterY ? mViewCenterX : mViewCenterY;
mSymbolPaint.setStrokeWidth(mRadius * 2 / 18);
setMeasuredDimension(mViewWidth, mViewHeight);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (mViewWidth != 0) {
// 为八个点定坐标
mPoint0 = new PointF(mRadius / 2, mRadius / 2);
mPoint1 = new PointF(mRadius / 2 * 3, mRadius / 2);
mPoint2 = new PointF(mRadius / 2, mRadius / 2 * 2);
mPoint3 = new PointF(mRadius / 2 * 3, mRadius / 2 * 2);
mPoint4 = new PointF(mRadius / 2, mRadius / 2 * 3);
mPoint5 = new PointF(mRadius / 2 * 3, mRadius / 2 * 3);
mPoint6 = new PointF(mRadius / 2 * 2, mRadius / 2);
mPoint7 = new PointF(mRadius / 2 * 2, mRadius / 2 * 3);
}
super.onSizeChanged(w, h, oldw, oldh);
}

private int measureHeight(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
int result = 500;
if (specMode == MeasureSpec.AT_MOST) {
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
}

private int measureWidth(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
int result = 500;
if (specMode == MeasureSpec.AT_MOST) {
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
return result;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 先绘制背景
canvas.drawCircle(mViewCenterX, mViewCenterY, mRadius, mCirclePaint);
if (mIsDrawArrow) {
if (mTickToArrawPoint1 == null) {
// 刚开始直接绘制箭头
drawLine(canvas, mPoint2, mPoint3);
drawLine(canvas, mPoint3, mPoint6);
drawLine(canvas, mPoint3, mPoint7);
} else {
// 偏移过程绘制对勾到箭头过程
drawLine(canvas, mPoint2, mTickToArrawPoint1);
drawLine(canvas, mPoint7, mTickToArrawPoint2);
drawLine(canvas, mTickToArrawPoint3, mTickToArrawPoint4);
}
} else {
// 偏移过程绘制箭头到对勾过程
drawLine(canvas, mPoint2, mArrawToTickPoint1);
drawLine(canvas, mPoint7, mArrawToTickPoint2);
drawLine(canvas, mArrawToTickPoint3, mArrawToTickPoint4);
}

}

// 计算对勾到箭头过程对应落点的坐标值
private void tickToArraw() {

mProgress = 1 - mProgress;

double length = Math.sqrt(2) / 2 * mRadius - mProgress * Math.sqrt(2) / 2 * mRadius;

mTickToArrawPoint1 = new PointF((float) (mRadius + (mRadius / 2 - length * Math.sqrt(2) / 2)), (float) (mRadius * 3 / 2 - (mRadius / 2 - length * Math.sqrt(2) / 2)));

mTickToArrawPoint2 = new PointF((float) (mRadius * 5 / 4 + mRadius / 4 * mProgress), (float) mRadius);

mTickToArrawPoint3 = new PointF((float) (mRadius * 3 / 2 - mRadius / 2 * mProgress), (float) mRadius / 2);

mTickToArrawPoint4 = new PointF((float) (mRadius * 5 / 4 + mRadius / 4 * mProgress), (float) mRadius);

}

// 计算箭头到对勾过程对应落点的坐标值
private void arrawToTick() {

double length = mProgress * Math.sqrt(2) / 2 * mRadius;

mArrawToTickPoint1 = new PointF((float) (mRadius * 3 / 2 - length * Math.sqrt(2) / 2), (float) (mRadius + length * Math.sqrt(2) / 2));

mArrawToTickPoint2 = new PointF((float) (mRadius * 3 / 2 - mRadius / 4 * mProgress), (float) mRadius);

mArrawToTickPoint3 = new PointF((float) (mRadius + mRadius / 2 * mProgress), (float) mRadius / 2);

mArrawToTickPoint4 = new PointF((float) (mRadius * 3 / 2 - mRadius / 4 * mProgress), (float) mRadius);

}

/**
* 设置button图标变化的进度
*
* @param isDrawArrow 绘制监听
* @param progress    进度
*/
public void setProgress(boolean isDrawArrow, float progress) {
mIsDrawArrow = isDrawArrow;
mProgress = progress;
if (isDrawArrow) {
tickToArraw();
} else {
arrawToTick();
}
invalidate();
}

// 绘制线段
private void drawLine(Canvas canvas, PointF start, PointF end) {
if (start.x != 0 && end.x != 0) {
canvas.drawLine(start.x, start.y, end.x, end.y, mSymbolPaint);
}
}
}


接下来对代码进行详细分析:



上面数字对应mPoint对应的点

比如我们刚开始绘制的箭头,代码为

drawLine(canvas, mPoint2, mPoint3);
drawLine(canvas, mPoint3, mPoint6);
drawLine(canvas, mPoint3, mPoint7);


其实就是点2连3、点3连6、点3连7,就出来了箭头效果

由箭头随偏差变成对勾的过程如下:









可以看到2和7是固定的,①23趋势是向左下滑动致27,②73是3滑动到2分之3半径处,③63是两点均移动:6移动到1,3移动到2分之3半径处,当它们滑动完成后便变为了对勾;反之对勾变为箭头逆向过程而已。

需要注意的是线移动的过程中的点是对应一定数学关系的,23的3点移动的过程是一直沿着最原始的73这条线滑动的,这里用直角关系可方便求出当前坐标点。

//使用十分方便
<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=".MainActivity">

<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">

</android.support.v4.view.ViewPager>

<test.oubowu.com.magicbutton.MagicButton
android:id="@+id/button"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"/>

</RelativeLayout>


mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mViewPager.getCurrentItem() == mCount - 1) {
// 导航完成
Toast.makeText(MainActivity.this, "导航完毕", Toast.LENGTH_LONG).show();
} else if (mViewPager.getCurrentItem() == mCount - 2) {
SCROLL_STATE = SCROLL_RIGHT;
mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
} else {
mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
}
}
});

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
switch (SCROLL_STATE) {
case SCROLL_RIGHT:
//向右滑动说明是从倒数第一页滑动倒数第二页,调用mButton.setProgress(true, positionOffset),true为由对勾绘制箭头,将偏移量传回来绘制
if (position == mCount - 2) {
mButton.setProgress(true, positionOffset);
}
break;
case SCROLL_LEFT:
//向左滑动说明是从倒数第二页滑动倒数第一页,调用mButton.setProgress(false, positionOffset),false为由箭头绘制对勾,将偏移量传回来绘制
if (position == mCount - 2) {
mButton.setProgress(false, positionOffset);
}
break;
case SCROLL_NONE:

break;
}
}

@Override
public void onPageSelected(int i) {

}

@Override
public void onPageScrollStateChanged(int i) {

}
});


完整demo资源地址:http://download.csdn.net/detail/oushangfeng123/8917893
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  自定义 android