人间烟火气,最抚凡人心。 — 网络
写在前面
2020大半年没有更新,2021年初我又来了,回顾一下2020年,年初立下的Flag,也算是完成了几个,1.多吃蔬菜水果;2.戒烟少喝酒;3.坚持读书;4.体重回到了大学毕业时的水平。还有没完成的Flag,1.当工作任务很多的时候,没有抱着学习的态度去工作,而是为了完成任务而工作,没有收获;2.没有坚持运动;3.偶尔还是不能调整好心态。当然还有其他的收获,1.驾照到手;2.买新房,开始还房贷的生活;3.向心仪的女孩子摊牌成功;4.报自考专升本。
现在进入正题,之前做过一个项目,有注册账号的功能,但是注册之前要通过滑块验证码,先来说说设计原理,云端会返回给我两张同高不同宽的图片,一张是有滑动块缺口的全部图片,一张是滑动块图片,我只需要横向滑动将横坐标传回给云端进行验证即可。这个设计思路清晰明了,下面来说一下我的实现思路,首先想到的就是自定义View,往自定义View中添加两个ImageView用来显示图片内容,其中显示滑动块图片的ImageView可以通过触摸事件进行横向移动,起始位置为最左边,通过调整显示滑动块图片的ImageView的左边距达到横向移动的效果。
具体实现
创建SliderView继承自FrameLayout,创建两个ImageView,通过计算手指移动的距离调整横向移动。
public class SliderView extends FrameLayout {
// 背景图(固定的)
private ImageView mInvariableView;
// 滑动块(可移动)
private ImageView mVariableView;
// 触摸抬起事件监听器
private OnTouchUpListener mOnTouchUpListener;
// 滑动块的左边距
private int mVariableLeftMargin;
// 滑动块可以滑动的范围
private int mVariableDistance;
// 记下手指按下的位置
private int mDownX;
public SliderView(@NonNull Context context) {
this(context, null);
}
public SliderView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SliderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SliderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
/**
* 初始化
* @param context
*/
private void init(Context context) {
// 创建背景图控件
mInvariableView = new ImageView(context);
mInvariableView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(mInvariableView);
// 创建滑动块控件
mVariableView = new ImageView(context);
mVariableView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(mVariableView);
}
/**
* 移动滑动块
* 原理:通过设置滑动块的左边距达到移动的效果
* @param margin 左边距
*/
private void move(int margin) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) mVariableView.getLayoutParams();
marginLayoutParams.leftMargin = margin;
mVariableView.setLayoutParams(marginLayoutParams);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手下按下时,记录当前位置
mDownX = (int) event.getX();
// 同时记录滑动块的左边距
mVariableLeftMargin = ((MarginLayoutParams) mVariableView.getLayoutParams()).leftMargin;
break;
case MotionEvent.ACTION_MOVE:
// 计算手指移动的距离
int distance = (int) (event.getX() - mDownX);
// 如果左边距不为0,说明在此次ACTON_DOWN事件产生之前,滑动块已经不在初始位置,已经滑动过一次或多次,
// 要将已产生的左边距加上,避免丢失上一次滑动的距离,导致滑动块从初始位置移动
if (mVariableLeftMargin != 0) distance = mVariableLeftMargin + distance;
// 避免手指移动的距离过大,滑动块超出背景图的范围,保证滑动块一直在背景图内移动
if (distance < 0) distance = 0;
else if (distance > mVariableDistance) distance = mVariableDistance;
// 开始移动滑动块
move(distance);
break;
case MotionEvent.ACTION_UP:
if (mOnTouchUpListener == null)
throw new NullPointerException("You have to set the OnTouchUpListener!");
mOnTouchUpListener.onTouchUp(mVariableView.getLeft());
break;
default:
break;
}
return true;
}
/**
* 重置滑动块位置
*/
public void reset() {
move(0);
}
/**
* 设置图片
* @param invariableBitmap 固定的背景图
* @param variableBitmap 滑动块图片
*/
public void setSliderBitmap(Bitmap invariableBitmap, Bitmap variableBitmap) {
mInvariableView.setImageBitmap(invariableBitmap);
mVariableView.setImageBitmap(variableBitmap);
// 计算滑动块可以移动的范围
mVariableDistance = invariableBitmap.getWidth() - variableBitmap.getWidth();
}
/**
* 设置触摸抬起事件监听器
* @param listener 触摸抬起事件监听器
*/
public void setOnTouchUpListener(OnTouchUpListener listener) {
mOnTouchUpListener = listener;
}
public interface OnTouchUpListener {
void onTouchUp(int x);
}
}
如何使用
下面通过一个Demo演示如何使用该自定义View。
1.创建布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.demo.slider.SliderView
android:id="@+id/view_slider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
2.创建Activity
新建Activity,重写onCreate函数,调用setContentView指定布局,初始化View并SliderView设置监听器和滑动块图片。
public class MainActivity extends AppCompatActivity implements SliderView.OnTouchUpListener {
private SliderView mSliderView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
// 这里涉及到内部机密,就用两个本地图片代替了,放在drawable资源目录下
Bitmap invariableBitmap = DrawableUtil.drawableToBitmap(getResources().getDrawable(R.drawable.invariable));
Bitmap variableBitmap = DrawableUtil.drawableToBitmap(getResources().getDrawable(R.drawable.variable));
mSliderView = findViewById(R.id.view_slider);
mSliderView.setOnTouchUpListener(this);
mSliderView.setSliderBitmap(invariableBitmap, variableBitmap);
}
@Override
public void onTouchUp(int x) {
// 在这个回调里将横坐标传回给云端,进行验证
Log.d(MainActivity.class.getSimpleName(), "onTouchUp : x = " + x);
}
}
运行效果如下:
最后
开篇既然说了关于个人,那就再说说吧,毕竟要有始有终,我对2020年整体是满意的,2021年希望能把2020年没有完成的Flag完成,赚钱多多,成长多多。2020因为疫情注定不平凡,2021年因为疫情或许不能回家过年,希望疫情早日过去,大家都能够正常生活。