遇到个需要仿荷包这个滑块的需求
第一想法使用seekbar来实现,结果android:tickMark属性需要API24以上,瞬间无语了,API24的用户目前又有几个呢?
然后想自定义seekbar,结果做出来后拖动的时候一闪一闪的,没有中间过程。
突然想起鸿神的一篇文章里有个拖动控件的ViewDragHelper,http://blog.youkuaiyun.com/lmj623565791/article/details/46858663;
稍微修改一下不就是我所需要的吗,我把它里面的多余的去掉,就留了一个回弹的view,然后修改了下回弹的位置,并且用canvas画出几个圆点,大功告成。
先贴上我的布局文件,中间那条线是单独的一个view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_centerVertical="true"
android:background="@color/colorAccent" />
<com.tshouyi.vdhdemo.VDHLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left"
android:background="@mipmap/bg"
android:clickable="true"
android:gravity="center"
android:text="back" />
</com.tshouyi.vdhdemo.VDHLayout>
</RelativeLayout>
代码注释也很详细了,没注释的地方我也不知道
package com.tshouyi.vdhdemo;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* Created by zxy on 2016/11/11.
*/
public class VDHLayout extends LinearLayout {
private ViewDragHelper mDragger;
private View mAutoBackView;
private Point[] mAutoBackOriginPos =
{
new Point(0, 0),
new Point(0, 0),
new Point(0, 0),
new Point(0, 0),
new Point(0, 0),
};//5个原点坐标数组初始值
public VDHLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);//没这个方法就不执行onDraw,我也不知道为什么
//1、创建实例
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback()//3、实现ViewDragHelper.CallCack相关方法
{
/**
* 如果子View不消耗事件(clickable=false),那么整个手势(DOWN-MOVE*-UP)都是直接进入onTouchEvent,在onTouchEvent的DOWN的时候就确定了captureView。
* 如果消耗事件,那么就会先走onInterceptTouchEvent方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:
* getViewHorizontalDragRange和getViewVerticalDragRange,只有这两个方法返回大于0的值才能正常的捕获。
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {//tryCaptureView如果返回ture则表示可以捕获该view
//mEdgeTrackerView禁止直接移动
return child == mAutoBackView;
}
/**
* clampViewPositionHorizontal,clampViewPositionVertical可以在该方法中对child移动的边界进行控制,
* left , top 分别为即将移动到的位置,以下代码表示内部移动效果
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mAutoBackView.getWidth() - leftBound;
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return mAutoBackOriginPos[0].y;//水平滑动,y是固定的
}
//手指释放的时候回调
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//mAutoBackView手指释放时可以自动回去
if (releasedChild == mAutoBackView) {
//判断不同范围回到最近的圆点
if (mAutoBackView.getLeft() < mAutoBackOriginPos[1].x / 2) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos[0].x, mAutoBackOriginPos[0].y);
} else if (mAutoBackView.getLeft() < 3 * mAutoBackOriginPos[1].x / 2) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos[1].x, mAutoBackOriginPos[0].y);
} else if (mAutoBackView.getLeft() < 5 * mAutoBackOriginPos[1].x / 2) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos[2].x, mAutoBackOriginPos[0].y);
} else if (mAutoBackView.getLeft() < 7 * mAutoBackOriginPos[1].x / 2) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos[3].x, mAutoBackOriginPos[0].y);
} else {
mDragger.settleCapturedViewAt(mAutoBackOriginPos[4].x, mAutoBackOriginPos[0].y);
}
invalidate();
}
}
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
});
}
// 2、触摸相关方法
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mDragger.shouldInterceptTouchEvent(event);//决定我们是否应该拦截当前的事件
}
int position;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getX() > mAutoBackView.getLeft() && event.getX() < mAutoBackView.getRight()) {//按下时在滑块范围内说明要拖动
mDragger.processTouchEvent(event);//处理触摸事件
} else {//else滑动滑块到触摸位置最近的圆点
if (event.getX() - 60 < mAutoBackOriginPos[1].x / 2) {
//点击该范围移动到第一个点,60是什么我也不知道,不要这个就点不准
position = 0;
} else if (event.getX() - 60 < 3 * mAutoBackOriginPos[1].x / 2) {
position = mAutoBackOriginPos[1].x;
} else if (event.getX() - 60 < 5 * mAutoBackOriginPos[1].x / 2) {
position = 2 * mAutoBackOriginPos[1].x;
} else if (event.getX() - 60 < 7 * mAutoBackOriginPos[1].x / 2) {
position = 3 * mAutoBackOriginPos[1].x;
} else {
position = 4 * mAutoBackOriginPos[1].x;
}
//1.调用ofInt(int...values)方法创建ValueAnimator对象
int pNow = mAutoBackView.getLeft();
ValueAnimator mAnimator = ValueAnimator.ofInt(pNow, position);
//2.为目标对象的属性变化设置监听器
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 3.为目标对象的属性设置计算好的属性值
int animatorValue = (int) animation.getAnimatedValue();
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) mAutoBackView.getLayoutParams();
marginLayoutParams.leftMargin = animatorValue;
mAutoBackView.setLayoutParams(marginLayoutParams);
}
});
//4.设置动画的持续时间、是否重复及重复次数等属性
mAnimator.setDuration(Math.abs(pNow - position) * 2);
mAnimator.setRepeatCount(0);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
//5.为ValueAnimator设置目标对象并开始执行动画
mAnimator.setTarget(mAutoBackView);
mAnimator.start();
invalidate();
}
return true;
}
@Override
public void computeScroll() {
if (mDragger.continueSettling(true)) {
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//保存5个原点的位置信息(左边缘坐标),不懂的参考图片
for (int i = 0; i < 5; i++) {
mAutoBackOriginPos[i].x = i * ((r - l) - (mAutoBackView.getRight() - mAutoBackView.getLeft())) / 4;
mAutoBackOriginPos[i].y = mAutoBackView.getTop();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mAutoBackView = getChildAt(0);//加载view
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
//画圆点
for (int i = 0; i < 5; i++) {
canvas.drawCircle(mAutoBackOriginPos[i].x + (mAutoBackView.getRight() - mAutoBackView.getLeft()) / 2,
mAutoBackOriginPos[i].y + (mAutoBackView.getBottom() - mAutoBackView.getTop()) / 2, 15, paint);
}
}
}
不懂的再看一下这张图吧
好了,就这样吧。
顺便把另外一种自定义seek利用seek的进度的方式贴上,原理就是通过改变seekbar的进度来滑动到指定位置。
先看xml文件,两个seekbar重合,上面seekbar个仅仅是为了得到圆点,也就是将Base.Widget.AppCompat.SeekBar.Discrete重新定义了一下,第二个的最大值设置为200
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
<SeekBar
android:id="@+id/seekBar2"
style="@style/Base.Widget.AppCompat.SeekBar.Seek"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:max="4"
android:thumb="@null" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:max="200"
android:progress="0"
android:thumb="@mipmap/ic_launcher" />
</RelativeLayout>
接下来是自定义的样式
<style name="Base.Widget.AppCompat.SeekBar.Seek">
<item name="tickMark">@drawable/seekbar</item>
</style>
引用的shape文件
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="12dp"
android:height="12dp" />
<solid android:color="@android:color/holo_red_dark" />
</shape>
activity也很简单,自己看吧
public class MainActivity extends AppCompatActivity {
private SeekBar seekBar;
boolean check = false;
int pro;//用来控制滑动时触发的onProgressChanged不进行滑动
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar = (SeekBar) findViewById(R.id.seekBar);
SeekBar seekBar2 = (SeekBar) findViewById(R.id.seekBar2);
seekBar2.setEnabled(false);//去除重叠的后面个seekbar的影响
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final SeekBar seekBar, int i, boolean b) {
if (pro==0||pro == seekBar.getProgress() || pro - 1 == seekBar.getProgress()|| pro+ 1 == seekBar.getProgress() ) {
//变化值为1的一般不是滑动触发的
final int progress = seekBar.getProgress();
pro = progress;
if (progress < 25) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (progress != 0)
seekBar.setProgress(progress - 1);
}
}, 10);
} else if (progress < 50) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
seekBar.setProgress(progress + 1);
}
}, 10);
} else if (progress < 75) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (progress != 50)
seekBar.setProgress(progress - 1);
}
}, 10);
} else if (progress < 100) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
seekBar.setProgress(progress + 1);
}
}, 10);
} else if (progress < 125) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (progress != 100)
seekBar.setProgress(progress - 1);
}
}, 10);
} else if (progress < 150) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
seekBar.setProgress(progress + 1);
}
}, 10);
} else if (progress < 175) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (progress != 150)
seekBar.setProgress(progress - 1);
}
}, 10);
} else if (progress < 200) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
seekBar.setProgress(progress + 1);
}
}, 10);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
pro=seekBar.getProgress();
seekBar.setProgress(seekBar.getProgress() - 1);
}
});
}
}
最后效果
没怎么写博客,里面问题很多,欢迎指正