【关键词】
自定义View
次数选择器
滑动
【问题】
- 实现一个可滑动的次数选择器;
【效果图】
「原型图」
「实现图」
【分析】
- 对外提供简单的Change监听接口;
- 如果处于两者之间就需要做判断:大于一半就自动跳转到下一个,小于一半,则回到上一个;
- 通过
Scroller
及其startScroll()
方法来实现回弹效果; - 要灵活控制刻度的最小值和最大值,因为可能随着需求的更改,这个值很容易发生改变(即处理好对应关系);
- 在自定义View中只画刻度和文字,至于红色的指针和外面的透明渐变图层则可以直接在布局文件中实现:
一方面比起在
java代码
中更容易实现,另一方面更灵活(如果想要去掉渐变图层,只需要修改布局文件,而不需要对java代码
进行任何修改);
【解决方案】
- 我最开始是通过不断的设置
padding
来实现,但是一时头脑不清晰,没有处理好对应关系,就重新改用scroll
来实现了;
【代码】
「客户端用法代码」
布局代码activity_count_view.xml
" data-snippet-id="ext.034c2d7ad689e18b2717192d3945a52a" data-snippet-saved="false" data-codota-status="done"><?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="100dp"
android:background="#ffffff"
android:orientation="vertical">
<!-- 刻度 -->
<com.lyloou.android.view.CountView
android:id="@+id/cv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_marginEnd="48dp"
android:layout_marginStart="48dp"
android:background="#ffffff" />
<!-- 中间的指针 -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/reminder_slider" />
<!-- 遮罩层 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="horizontal">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:background="#ffffff" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:background="@drawable/gradient_count_view" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:background="#ffffff" />
</LinearLayout>
</RelativeLayout>
Activity对布局文件的初始化
public class CountViewActivity extends AppCompatActivity {
private Activity mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_count_view);
initView();
}
private void initView() {
CountView countView = (CountView) findViewById(R.id.cv_main);
if (countView != null) {
// int index = countView.getIndex(); // 获取当前值
// countView.setIndex(6);// 设置默认值
countView.setOnChangeListener(new CountView.OnChangeListener() {
@Override
public void doIndex(int index) {
Utoast.show(mContext, "选中了:" + index + "次");
}
});
}
}
}
中间的渐变shape gradient_count_view.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="180"
android:centerColor="#10ffffff"
android:endColor="#ffffff"
android:startColor="#ffffff" />
</shape>
「通用源代码」
自定义View
= (MAX_ITEM - MIN_ITEM)) {
index = MAX_ITEM - MIN_ITEM;
smoothScrollTo(index * W);
} else {
int shift = scrollX % W;
if (shift >= W / 2) {
index++;
smoothScrollBy(W - shift);
} else {
smoothScrollBy(-shift);
}
}
return index;
}
public void setIndex(int index) {
int newIndex = index - MIN_ITEM;
scrollTo(newIndex * W, 0);
}
public void setOnChangeListener(OnChangeListener listener) {
mChangeListener = listener;
}
private void smoothScrollTo(int dextX) {
smoothScrollBy(dextX - getScrollX());
}
private void smoothScrollBy(int deltaX) {
mScroller.startScroll(getScrollX(), 0, deltaX, 0, 320);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller != null && mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int shift = getWidth() / 2 - W / 2; // 将原点移到屏幕中间;
int height = getHeight();
for (int i = MIN_ITEM - EXTRA_COUNT; i = MIN_ITEM && i package com.lyloou.android.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import com.lyloou.android.util.Uscreen;
public class CountView extends View {
public interface OnChangeListener {
void doIndex(int index);
}
private final Context CONTEXT = getContext();
private final int W = Uscreen.dp2Px(CONTEXT, 36);// 每一项的固定长度
private final int LINE_H = Uscreen.dp2Px(CONTEXT, 6); // 长直线的厚度
private final int ITEM_LINE_W = LINE_H / 3; // 竖直刻度直线的厚度
private final int ITEM_LINE_H = Uscreen.dp2Px(CONTEXT, 16); // 竖直刻度直线的高度
private final int BOTTOM_FONT_SIZE = (int) Uscreen.sp2Px(CONTEXT, 12); // 底部的文字大小
private static final int COLOR_LINE = Color.parseColor("#4422f9"); // 长直线和刻度线的颜色
private static final int MIN_ITEM = 1; // 显示的最小刻度值
private static final int MAX_ITEM = 20; // 显示的最大刻度值
private static final int EXTRA_COUNT = 2; // 左边和右边额外的刻度个数
private Paint mLinePaint;
private TextPaint mTextPaint;
private Scroller mScroller;
private OnChangeListener mChangeListener;
public CountView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setUp();
initData();
}
public CountView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CountView(Context context) {
this(context, null);
}
private void setUp() {
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setColor(COLOR_LINE);
mTextPaint = new TextPaint();
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(BOTTOM_FONT_SIZE);
mScroller = new Scroller(CONTEXT);
}
private void initData() {
setIndex(MIN_ITEM);
}
private int mLastX;
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int deltaX = x - mLastX;
scrollBy(-deltaX, 0);
mLastX = x;
break;
case MotionEvent.ACTION_UP:
doIndex(getIndex());
break;
}
return true;
}
private void doIndex(int index) {
if (mChangeListener != null) {
mChangeListener.doIndex(index + MIN_ITEM);
}
}
public int getIndex() {
int index = 0;
int scrollX = getScrollX();
index = scrollX / W;
if (scrollX <= 0) {
index = 0;
smoothScrollTo(index);
} else if (index >= (MAX_ITEM - MIN_ITEM)) {
index = MAX_ITEM - MIN_ITEM;
smoothScrollTo(index * W);
} else {
int shift = scrollX % W;
if (shift >= W / 2) {
index++;
smoothScrollBy(W - shift);
} else {
smoothScrollBy(-shift);
}
}
return index;
}
public void setIndex(int index) {
int newIndex = index - MIN_ITEM;
scrollTo(newIndex * W, 0);
}
public void setOnChangeListener(OnChangeListener listener) {
mChangeListener = listener;
}
private void smoothScrollTo(int dextX) {
smoothScrollBy(dextX - getScrollX());
}
private void smoothScrollBy(int deltaX) {
mScroller.startScroll(getScrollX(), 0, deltaX, 0, 320);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller != null && mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int shift = getWidth() / 2 - W / 2; // 将原点移到屏幕中间;
int height = getHeight();
for (int i = MIN_ITEM - EXTRA_COUNT; i < MAX_ITEM + EXTRA_COUNT; i++) {
int startX = (i - MIN_ITEM) * W + shift;
// 画直线
mLinePaint.setStrokeWidth(LINE_H);
canvas.drawLine(startX, height / 2, startX + W, height / 2, mLinePaint);
// 画刻度
mLinePaint.setStrokeWidth(ITEM_LINE_W);
canvas.drawLine(startX + W / 2, height / 2 - ITEM_LINE_H, startX + W / 2, height / 2, mLinePaint);
// 在刻度范围内,才画文字
if (i >= MIN_ITEM && i <= MAX_ITEM) {
String text = i + "次";
float textWidth = mTextPaint.measureText(text);
float textHeight = mTextPaint.getFontMetrics().bottom;
canvas.drawText(text, startX + (W - textWidth) / 2, (height + textHeight) / 2 + textHeight * 10,
mTextPaint);
}
}
}
}
【参考资料】
- 回弹效果的代码参考了《Android开发艺术探索》P136
时间: 2024-10-06 06:45:44