Android实现底部滚轮式选择弹跳框
作者:丁氏开发工作室
这篇文章主要为大家详细介绍了Android实现底部滚轮式选择弹跳框,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了Android实现底部滚轮式选择弹跳框的具体代码,供大家参考,具体内容如下
先看效果:
调用方法:
SlideDialog slideDialog = new SlideDialog(this, list, false, false); slideDialog.setOnSelectClickListener(new SlideDialog.OnSelectListener() { @Override public void onCancel() { Toast.makeText(GroupFormListActivity.this, "未选择", Toast.LENGTH_SHORT).show(); } @Override public void onAgree(String txt) { Toast.makeText(GroupFormListActivity.this, "已选中", Toast.LENGTH_SHORT).show(); } }); slideDialog.show();
自定义SlideDialog
package xxx.xxx.xxx.xxx; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.TextView; import androidx.annotation.NonNull; import com.txh.yunyao.R; import com.txh.yunyao.common.views.EasyPickerView; import java.util.ArrayList; import java.util.List; /** * 底部滑动选择弹跳框 */ public class SlideDialog extends Dialog { private boolean isCancelable = false; private boolean isBackCancelable = false; private Context mContext; //上下文 private List<String> list = new ArrayList<>(0); //数据 private int selectPos; //默认选中位置 private OnSelectListener mSelectListener; //监听 public SlideDialog(@NonNull Context context, int view, List<String> list, boolean isCancelable, boolean isBackCancelable) { super(context, R.style.SlideDialog); this.mContext = context; this.isCancelable = isCancelable; this.isBackCancelable = isBackCancelable; this.list = list; this.selectPos = 0; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置View setContentView(R.layout.select_slide_template); //设置点击物理返回键是否可关闭弹框 setCancelable(isCancelable); //设置点击弹框外是否可关闭弹框 setCanceledOnTouchOutside(isBackCancelable); //设置view显示位置 Window window = this.getWindow(); window.setGravity(Gravity.BOTTOM); WindowManager.LayoutParams params = window.getAttributes(); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; window.setAttributes(params); //初始化控件 TextView tv_cancel = findViewById(R.id.tv_cancel); TextView tv_agree = findViewById(R.id.tv_agree); EasyPickerView pickerView = findViewById(R.id.pickerView); //取消点击事件 tv_cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消 mSelectListener.onCancel(); dismiss(); } }); //确认点击事件 tv_agree.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //确认 mSelectListener.onAgree(list.get(selectPos)); dismiss(); } }); //设置数据 pickerView.setDataList(list); //监听数据 pickerView.setOnScrollChangedListener(new EasyPickerView.OnScrollChangedListener() { @Override public void onScrollChanged(int curIndex) { //滚动时选中项发生变化 } @Override public void onScrollFinished(int curIndex) { //滚动结束 selectPos = curIndex; } }); } public interface OnSelectListener { //取消 void onCancel(); //确认 void onAgree(String txt); } public void setOnSelectClickListener(OnSelectListener listener) { this.mSelectListener = listener; } }
R.style.SlideDialog
<style name="SlideDialog" parent="@android:style/Theme.Holo.Dialog"> <!-- 是否有边框 --> <item name="android:windowFrame">@null</item> <!--是否在悬浮Activity之上 --> <item name="android:windowIsFloating">true</item> <!-- 标题 --> <item name="android:windowNoTitle">true</item> <!--阴影 --> <item name="android:windowIsTranslucent">true</item><!--半透明--> <!--背景透明--> <item name="android:windowBackground">@android:color/transparent</item> <!-- 还可以加入一些弹出和退出的动画 (lan)--> </style>
R.layout.select_slide_template
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@color/white" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/dp_10" android:layout_marginRight="@dimen/dp_10" android:paddingTop="@dimen/dp_10" android:paddingLeft="@dimen/dp_15" android:paddingRight="@dimen/dp_15"> <TextView android:id="@+id/tv_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/picture_cancel" android:padding="3dp" android:textColor="@color/black" /> <TextView android:id="@+id/tv_agree" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="@string/picture_confirm" android:padding="3dp" android:textColor="@color/black" /> </RelativeLayout> <com.txh.yunyao.common.views.EasyPickerView android:id="@+id/pickerView" android:layout_width="match_parent" android:layout_height="wrap_content" app:epvMaxShowNum="3" android:layout_marginBottom="@dimen/dp_15" android:layout_marginTop="@dimen/dp_15" app:epvTextColor="@color/black" app:epvTextPadding="@dimen/dp_10" app:epvRecycleMode="true" app:epvTextSize="14dp"/> </LinearLayout>
自定义EasyPickerView支持以下几个属性:
- epvTextSize:字符的大小
- epvTextColor:字符的颜色
- epvTextPadding:字符的间距
- epvTextMaxScale:中间字符缩放的最大值
- epvTextMinAlpha:两端字符最小alpha值
- epvRecycleMode:是否为循环模式
- epvMaxShowNum:显示多少个字符
自定义EasyPickerView
package xxx.xxx.xxx.xxx.xxx; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.text.TextPaint; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.widget.Scroller; import com.txh.yunyao.R; import java.util.ArrayList; import java.util.List; /** * 滚轮视图,可设置是否循环模式,实现OnScrollChangedListener接口以监听滚轮变化 */ public class EasyPickerView extends View { // 文字大小 private int textSize; // 颜色,默认Color.BLACK private int textColor; // 文字之间的间隔,默认10dp private int textPadding; // 文字最大放大比例,默认2.0f private float textMaxScale; // 文字最小alpha值,范围0.0f~1.0f,默认0.4f private float textMinAlpha; // 是否循环模式,默认是 private boolean isRecycleMode; // 正常状态下最多显示几个文字,默认3(偶数时,边缘的文字会截断) private int maxShowNum; private TextPaint textPaint; private Paint.FontMetrics fm; private Scroller scroller; private VelocityTracker velocityTracker; private int minimumVelocity; private int maximumVelocity; private int scaledTouchSlop; // 数据 private List<String> dataList = new ArrayList<>(0); // 中间x坐标 private int cx; // 中间y坐标 private int cy; // 文字最大宽度 private float maxTextWidth; // 文字高度 private int textHeight; // 实际内容宽度 private int contentWidth; // 实际内容高度 private int contentHeight; // 按下时的y坐标 private float downY; // 本次滑动的y坐标偏移值 private float offsetY; // 在fling之前的offsetY private float oldOffsetY; // 当前选中项 private int curIndex; private int offsetIndex; // 回弹距离 private float bounceDistance; // 是否正处于滑动状态 private boolean isSliding = false; public EasyPickerView(Context context) { this(context, null); } public EasyPickerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public EasyPickerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EasyPickerView, defStyleAttr, 0); textSize = a.getDimensionPixelSize(R.styleable.EasyPickerView_epvTextSize, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); textColor = a.getColor(R.styleable.EasyPickerView_epvTextColor, Color.BLACK); textPadding = a.getDimensionPixelSize(R.styleable.EasyPickerView_epvTextPadding, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics())); textMaxScale = a.getFloat(R.styleable.EasyPickerView_epvTextMaxScale, 2.0f); textMinAlpha = a.getFloat(R.styleable.EasyPickerView_epvTextMinAlpha, 0.4f); isRecycleMode = a.getBoolean(R.styleable.EasyPickerView_epvRecycleMode, true); maxShowNum = a.getInteger(R.styleable.EasyPickerView_epvMaxShowNum, 3); a.recycle(); textPaint = new TextPaint(); textPaint.setColor(textColor); textPaint.setTextSize(textSize); textPaint.setAntiAlias(true); fm = textPaint.getFontMetrics(); textHeight = (int) (fm.bottom - fm.top); scroller = new Scroller(context); minimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); maximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity(); scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int mode = MeasureSpec.getMode(widthMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); contentWidth = (int) (maxTextWidth * textMaxScale + getPaddingLeft() + getPaddingRight()); if (mode != MeasureSpec.EXACTLY) { // wrap_content width = contentWidth; } mode = MeasureSpec.getMode(heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); contentHeight = textHeight * maxShowNum + textPadding * maxShowNum; if (mode != MeasureSpec.EXACTLY) { // wrap_content height = contentHeight + getPaddingTop() + getPaddingBottom(); } cx = width / 2; cy = height / 2; setMeasuredDimension(width, height); } @Override public boolean dispatchTouchEvent(MotionEvent event) { getParent().requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { addVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!scroller.isFinished()) { scroller.forceFinished(true); finishScroll(); } downY = event.getY(); break; case MotionEvent.ACTION_MOVE: offsetY = event.getY() - downY; if (isSliding || Math.abs(offsetY) > scaledTouchSlop) { isSliding = true; reDraw(); } break; case MotionEvent.ACTION_UP: int scrollYVelocity = 2 * getScrollYVelocity() / 3; if (Math.abs(scrollYVelocity) > minimumVelocity) { oldOffsetY = offsetY; scroller.fling(0, 0, 0, scrollYVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE); invalidate(); } else { finishScroll(); } // 没有滑动,则判断点击事件 if (!isSliding) { if (downY < contentHeight / 3) moveBy(-1); else if (downY > 2 * contentHeight / 3) moveBy(1); } isSliding = false; recycleVelocityTracker(); break; } return true; } @Override protected void onDraw(Canvas canvas) { if (null != dataList && dataList.size() > 0) { canvas.clipRect( cx - contentWidth / 2, cy - contentHeight / 2, cx + contentWidth / 2, cy + contentHeight / 2 ); // 绘制文字,从当前中间项往前、后一共绘制maxShowNum个字 int size = dataList.size(); int centerPadding = textHeight + textPadding; int half = maxShowNum / 2 + 1; for (int i = -half; i <= half; i++) { int index = curIndex - offsetIndex + i; if (isRecycleMode) { if (index < 0) index = (index + 1) % dataList.size() + dataList.size() - 1; else if (index > dataList.size() - 1) index = index % dataList.size(); } if (index >= 0 && index < size) { // 计算每个字的中间y坐标 int tempY = cy + i * centerPadding; tempY += offsetY % centerPadding; // 根据每个字中间y坐标到cy的距离,计算出scale值 float scale = 1.0f - (1.0f * Math.abs(tempY - cy) / centerPadding); // 根据textMaxScale,计算出tempScale值,即实际text应该放大的倍数,范围 1~textMaxScale float tempScale = scale * (textMaxScale - 1.0f) + 1.0f; tempScale = tempScale < 1.0f ? 1.0f : tempScale; // 计算文字alpha值 float textAlpha = textMinAlpha; if (textMaxScale != 1) { float tempAlpha = (tempScale - 1) / (textMaxScale - 1); textAlpha = (1 - textMinAlpha) * tempAlpha + textMinAlpha; } textPaint.setTextSize(textSize * tempScale); textPaint.setAlpha((int) (255 * textAlpha)); // 绘制 Paint.FontMetrics tempFm = textPaint.getFontMetrics(); String text = dataList.get(index); float textWidth = textPaint.measureText(text); canvas.drawText(text, cx - textWidth / 2, tempY - (tempFm.ascent + tempFm.descent) / 2, textPaint); } } } } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { offsetY = oldOffsetY + scroller.getCurrY(); if (!scroller.isFinished()) reDraw(); else finishScroll(); } } private void addVelocityTracker(MotionEvent event) { if (velocityTracker == null) velocityTracker = VelocityTracker.obtain(); velocityTracker.addMovement(event); } private void recycleVelocityTracker() { if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; } } private int getScrollYVelocity() { velocityTracker.computeCurrentVelocity(1000, maximumVelocity); int velocity = (int) velocityTracker.getYVelocity(); return velocity; } private void reDraw() { // curIndex需要偏移的量 int i = (int) (offsetY / (textHeight + textPadding)); if (isRecycleMode || (curIndex - i >= 0 && curIndex - i < dataList.size())) { if (offsetIndex != i) { offsetIndex = i; if (null != onScrollChangedListener) onScrollChangedListener.onScrollChanged(getNowIndex(-offsetIndex)); } postInvalidate(); } else { finishScroll(); } } private void finishScroll() { // 判断结束滑动后应该停留在哪个位置 int centerPadding = textHeight + textPadding; float v = offsetY % centerPadding; if (v > 0.5f * centerPadding) ++offsetIndex; else if (v < -0.5f * centerPadding) --offsetIndex; // 重置curIndex curIndex = getNowIndex(-offsetIndex); // 计算回弹的距离 bounceDistance = offsetIndex * centerPadding - offsetY; offsetY += bounceDistance; // 更新 if (null != onScrollChangedListener) onScrollChangedListener.onScrollFinished(curIndex); // 重绘 reset(); postInvalidate(); } private int getNowIndex(int offsetIndex) { int index = curIndex + offsetIndex; if (isRecycleMode) { if (index < 0) index = (index + 1) % dataList.size() + dataList.size() - 1; else if (index > dataList.size() - 1) index = index % dataList.size(); } else { if (index < 0) index = 0; else if (index > dataList.size() - 1) index = dataList.size() - 1; } return index; } private void reset() { offsetY = 0; oldOffsetY = 0; offsetIndex = 0; bounceDistance = 0; } /** * 设置要显示的数据 * * @param dataList 要显示的数据 */ public void setDataList(List<String> dataList) { this.dataList.clear(); this.dataList.addAll(dataList); // 更新maxTextWidth if (null != dataList && dataList.size() > 0) { int size = dataList.size(); for (int i = 0; i < size; i++) { float tempWidth = textPaint.measureText(dataList.get(i)); if (tempWidth > maxTextWidth) maxTextWidth = tempWidth; } curIndex = 0; } requestLayout(); invalidate(); } /** * 获取当前状态下,选中的下标 * * @return 选中的下标 */ public int getCurIndex() { return getNowIndex(-offsetIndex); } /** * 滚动到指定位置 * * @param index 需要滚动到的指定位置 */ public void moveTo(int index) { if (index < 0 || index >= dataList.size() || curIndex == index) return; if (!scroller.isFinished()) scroller.forceFinished(true); finishScroll(); int dy = 0; int centerPadding = textHeight + textPadding; if (!isRecycleMode) { dy = (curIndex - index) * centerPadding; } else { int offsetIndex = curIndex - index; int d1 = Math.abs(offsetIndex) * centerPadding; int d2 = (dataList.size() - Math.abs(offsetIndex)) * centerPadding; if (offsetIndex > 0) { if (d1 < d2) dy = d1; // ascent else dy = -d2; // descent } else { if (d1 < d2) dy = -d1; // descent else dy = d2; // ascent } } scroller.startScroll(0, 0, 0, dy, 500); invalidate(); } /** * 滚动指定的偏移量 * * @param offsetIndex 指定的偏移量 */ public void moveBy(int offsetIndex) { moveTo(getNowIndex(offsetIndex)); } /** * 滚动发生变化时的回调接口 */ public interface OnScrollChangedListener { public void onScrollChanged(int curIndex); public void onScrollFinished(int curIndex); } private OnScrollChangedListener onScrollChangedListener; public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) { this.onScrollChangedListener = onScrollChangedListener; } }
attrs中 EasyPickerView配置
<declare-styleable name="EasyPickerView"> <attr name="epvTextSize" format="dimension"/> <attr name="epvTextColor" format="color"/> <attr name="epvTextPadding" format="dimension"/> <attr name="epvTextMaxScale" format="float"/> <attr name="epvTextMinAlpha" format="float"/> <attr name="epvRecycleMode" format="boolean"/> <attr name="epvMaxShowNum" format="integer"/> </declare-styleable>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。