本文小编为大家详细介绍“怎么用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>
读到这里,这篇“怎么用Android实现底部滚轮式选择弹跳框”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。