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>
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Android開發TextView內的文字實現自動換行
- Android自定義View實現柱狀波形圖的繪制
- android實現倒計時動態圈
- android實現多點觸摸應用
- Android開發手冊Chip監聽及ChipGroup監聽