Android实现底部滚轮式选择弹跳框

2022-08-28 11:41:42

本文实例为大家分享了Android实现底部滚轮式选择弹跳框的具体代码,供大家参考,具体内容如下先看效果:调用方法:SlideDialogslideDialog=newSlideDialog(t...

本文实例为大家分享了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:windowIsFlpythonoating">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 javascript- 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) {sxzTigotA
        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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。