自定义view之无限滚动的刻度尺

具体思路是把一个view分成三段,当总长度>=40个刻度向左滚动,滚动到2/3的时候view移动到1/3出然后刷新显示的刻度这时为第一页,一次更新页数,当向右滚动的时候滚动且不为第一页则每滚动到1/3处view移动到2/3处。之后在添加一些首页和最后一页的判断。
这一次除了无限滚动还比我上一次写的多了手指滑动速度的判断,去掉了滚动结束自动归位到对应刻度的效果,代码中除了极端情况默认使用的view显示的刻度为30个,也就是说除了开始和结束,显示的位置始终在10~20之间,向右拖动图示:
无标题.png

效果展示:
GIF.gif

核心代码

package com.zqb.scrolldividingrule;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.Scroller;
import java.util.ArrayList;

/**
 * 横向滚动的刻度尺
 * Created by zhangqingbin on 2018/1/5.
 */

public class ScrollDividingRuleView extends View {

  private Scroller mScroller;
  private Paint mPaint;
  private VelocityTracker mVelocityTracker;
  private OnScrollListener mListener;
  private int mScaleMargin; //刻度间距
  private float mScaleWidth; //总刻度宽度
  private float mTextSize;//文字大小
  private ArrayList<String> mTextList;//刻度文字
  private ArrayList<String> mTotalTextList;//所有的刻度
  private float mLineHeight;//线高度
  private int mRectHeight;//总高度
  private int mScrollLastX;
  private int mInitDistance;//初始距离
  private int mInitPosition;//初始位置
  private int mTextLineMargin;//文字距线的距离
  private int mPage;//当前为复用所在页
  private long mLastInstance;//余数刻度
  private long mStartMoney;//开始金额
  private int mUnit;//每个小格的单位
  private long mFinalMoney;//结束金额

  public ScrollDividingRuleView(Context context) {
    this(context, null);
  }

  public ScrollDividingRuleView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public ScrollDividingRuleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    setWillNotDraw(false);
    init(context, attrs);
  }

  private void init(Context context, @Nullable AttributeSet attrs) {
    if (attrs != null) {
      for (int i = 0; i < attrs.getAttributeCount(); i++) {
        String name = attrs.getAttributeName(i);
        if ("layout_width".equals(name)) {
          String value = attrs.getAttributeValue(i);
          if (value.length() > 2) {
            if (value.endsWith("dp")) {
              float margin = Float.valueOf(value.substring(0, value.length() - 2));
              mScaleWidth = Utils.dp2px(context, margin);
            } else {
              mScaleWidth = Float.valueOf(value.substring(0, value.length() - 2));
            }
          } else if (value.equals("-1") || value.equals("-2") || value.equals("0")) {
            mScaleWidth = 0;
          }
        } else if ("line_height".equals(name)) {
          String value = attrs.getAttributeValue(i);
          if (value.length() > 2) {
            if (value.endsWith("dp")) {
              mLineHeight =
                  Utils.dp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
            } else {
              mLineHeight = Float.valueOf(value.substring(0, value.length() - 2));
            }
          } else {
            mLineHeight = 50;
          }
        } else if ("dividing_text_size".equals(name)) {
          String value = attrs.getAttributeValue(i);
          if (value.length() > 2) {
            if (value.endsWith("sp")) {
              mTextSize =
                  Utils.sp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
            } else {
              mTextSize = Float.valueOf(value.substring(0, value.length() - 2));
            }
          } else {
            mTextSize = 32;
          }
        }
      }
    }
    // 画笔
    mPaint = new Paint();
    //总的高度,因为text的高度和设置的textSize会有误差所以加上20的高度
    mRectHeight = (int) (mLineHeight + mTextSize + mTextLineMargin + 20);
    //初始设置每个刻度间距为30px
    mScaleMargin = 20;
    mTextList = new ArrayList<>();
    mTotalTextList = new ArrayList<>();
    mScroller = new Scroller(context);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    mPaint.setColor(Color.GRAY);
    // 抗锯齿
    mPaint.setAntiAlias(true);
    // 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
    mPaint.setDither(true);
    // 空心
    mPaint.setStyle(Paint.Style.STROKE);
    // 文字居中
    mPaint.setTextAlign(Paint.Align.CENTER);
    onDrawScale(canvas, mPaint); //画刻度
    onDrawLine(canvas, mPaint);//画刻度中间横线
    super.onDraw(canvas);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int height = MeasureSpec.makeMeasureSpec(mRectHeight, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, height);
    //初始化开始位置
    mInitDistance = getMeasuredWidth() / 2 - mInitPosition * mScaleMargin * 10;
  }

  @SuppressLint("ClickableViewAccessibility")
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(event);
    int x = (int) event.getX();
    int startX = 0;
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        if (mScroller != null) {//重新初始化fling效果,防止下一次移动有初始速度
          mScroller.fling(mScroller.getFinalX(), mScroller.getFinalY(), 0, 0, 0,
              (int) (mScaleWidth - mInitPosition * mScaleMargin * 10), 0, 0);
          mScroller.abortAnimation();
        }
        startX = x;
        mScrollLastX = x;
        return true;
      case MotionEvent.ACTION_MOVE:
        int dataX = mScrollLastX - x;
        smoothScrollBy(dataX, 0);
        mScrollLastX = x;
        return true;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        dealActionUp(x - startX);
        return true;
    }
    return super.onTouchEvent(event);
  }

  /**
   * 处理手势抬起之后的操作
   *
   * @param i 根据正负判断方向
   */
  private void dealActionUp(int i) {
    mVelocityTracker.computeCurrentVelocity(1000);
    if (mScroller.getFinalX() >= mScaleMargin * 10 * 10 * 2 - 10 * mScaleMargin && i > 0) {
      if (!notifyDataChanged(0)) {
        dealFling();
      }
    } else if (mScroller.getFinalX() <= mScaleMargin * 10 * 10 - 10 * mScaleMargin && i < 0) {
      if (!notifyDataChanged(1)) {
        dealFling();
      }
    } else {
      dealFling();
    }
    long i1 = mPage > 0 ? mPage * 100 : 0;
    if (mListener != null) {
      mListener.onScaleScrollChanged(
          mScroller.getFinalX() / mScaleMargin + mInitPosition * 10 + i1 + mStartMoney);//返回滚动选中的位置
    }
    mVelocityTracker.clear();
    mVelocityTracker.recycle();
    mVelocityTracker = null;
  }

  /**
   * 处理手指抬起之后的减速滚动
   */
  private void dealFling() {
    int minX;//最小值
    if (mPage == 0) {
      minX = -mInitPosition * mScaleMargin * 10;
    } else {
      minX = -mInitPosition * mScaleMargin * 10 + mScaleMargin * 10 * 10 - mScaleMargin * 10;
    }
    int maxX;//最大值 根据位置和总数改变
    if (mPage == (mTotalTextList.size() - 20) / 10 - 1 || mTotalTextList.size() < 40) {
      maxX = (int) (mScaleWidth - mInitPosition * mScaleMargin * 10);
    } else {
      maxX = mScaleMargin * 10 * 20 - mScaleMargin * 10;
    }
    mScroller.fling(mScroller.getFinalX(), mScroller.getFinalY(),
        -(int) mVelocityTracker.getXVelocity(), 0, minX, maxX, 0, 0);
  }

  private void onDrawScale(Canvas canvas, Paint paint) {
    paint.setAntiAlias(true);
    paint.setTextSize(mTextSize);
    paint.setStyle(Paint.Style.FILL);
    for (int i = 0, k = 0; i < mTextList.size() * 10; i++) {
      if (i < mTextList.size() * 10 - 9) {
        if (i % 10 == 0) { //整值
          paint.setColor(Color.GRAY);
          canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight,
              i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight - mTextLineMargin + 20,
              paint);
          //整值文字
          paint.setColor(Color.GRAY);
          canvas.drawText(mTextList.get(k), i * mScaleMargin + mInitDistance,
              mRectHeight - mLineHeight - mTextLineMargin, paint);
          k++;
        } else {
          paint.setColor(Color.GRAY);
          canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight,
              i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight / 2 - mTextLineMargin,
              paint);
        }
      } else {//画滚动到末尾余数 30200
        if (mPage == (mTotalTextList.size() - 20) / 10 - 1 || mTotalTextList.size() < 40) {
          if ((mLastInstance / mScaleMargin) > 0
              && (mLastInstance / mScaleMargin) > i - (mTextList.size() * 10 - 9)) {
            paint.setColor(Color.GRAY);
            canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight,
                i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight / 2 - mTextLineMargin,
                paint);
          }
        }
      }
    }
  }

  private void onDrawLine(Canvas canvas, Paint paint) {
    paint.setStrokeWidth(2);
    canvas.drawLine(mInitDistance, mRectHeight, mScaleWidth + mInitDistance, mRectHeight, paint);
  }

  /**
   * 使用Scroller的时候需要重写该方法
   */
  @Override
  public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), 0);
      postInvalidate();
    }
  }

  private void smoothScrollBy(int dx, int dy) {
    if (mScroller.getFinalX() >= mScaleMargin * 10 * 10 * 2 - 10 * mScaleMargin
        && dx > 0) {//向左滚动,判断是否滚动到第三部分起始位置
      if (!notifyDataChanged(0)) {
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
      }
    } else if (mScroller.getFinalX() <= mScaleMargin * 10 * 10 - 10 * mScaleMargin
        && dx < 0) {///向右滚动,判断是否滚动到第二部分开始位置
      if (!notifyDataChanged(1)) {
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
      }
    } else {//否则根据手指滚动
      mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
    }
    postInvalidate();
  }

  public interface OnScrollListener {
    void onScaleScrollChanged(long scale);
  }

  /**
   * 设置当前位置
   *
   * @param scale 刻度
   */
  public void setNowScale(float scale) {
    float i = scale * mScaleMargin;
    int maxPage = (mTotalTextList.size() - 20) / 10 - 1;
    mPage = 0;
    if (mTotalTextList.size() < 40) {//如果总数小于40 不需要处理多页
      mPage = 0;
      smoothScrollBy((int) (i - mScroller.getFinalX()), 0);
      return;
    } else if (i < mScaleMargin * 10 * 10 * 2 - mScaleMargin * 10) {//判断刻度是否在第一页
      mPage = 0;
    } else if (scale >= 10 * 10 + maxPage * 10 * 10) {//判断刻度是否在最后一页
      mPage = maxPage;
    } else {
      mPage = (int) (scale / 100 - 1);
    }
    if (scale * mUnit >= mFinalMoney) {//如果要设置的刻度大于集合里的最大值 则只滚动到末尾
      mPage = maxPage;
      mTextList.clear();
      for (int j = mPage * 10; j < mTotalTextList.size(); j++) {
        mTextList.add(mTotalTextList.get(j));
      }
      mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin + mLastInstance;
      smoothScrollBy((int) (mScaleWidth - mScroller.getFinalX()), 0);
    } else {
      if (mPage == (mTotalTextList.size() - 20) / 10 - 1) {//判断是否是最后一页
        if (mTotalTextList.size() >= mPage * 10 + 30) {
          mTextList.clear();
          for (int j = mPage * 10; j < mTotalTextList.size(); j++) {//更新刻度
            mTextList.add(mTotalTextList.get(j));
          }
          mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin + mLastInstance;//加上余数的宽度
          smoothScrollBy((int) (i - ((mPage + 1) * 10 * 10 * mScaleMargin) + 10 * 10 * mScaleMargin
              - mScroller.getFinalX()), 0);
        }
      } else {
        if (mTotalTextList.size() >= mPage * 10 + 30) {
          mTextList.clear();
          for (int j = mPage * 10; j < mPage * 10 + 30; j++) {//更新刻度
            mTextList.add(mTotalTextList.get(j));
          }
          mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin;
          smoothScrollBy((int) (i - ((mPage + 1) * 10 * 10 * mScaleMargin) + 10 * 10 * mScaleMargin
              - mScroller.getFinalX()), 0);
        }
      }
    }
  }

  /**
   * 初始化数据
   *
   * @param startMoney 开始金额
   * @param finalMoney 最终金额
   * @param unit 每个刻度单位
   * @param listener 滚动监听
   */
  public void bindMoneyData(int startMoney, int finalMoney, int unit, OnScrollListener listener) {
    if (mTotalTextList != null && mTotalTextList.size() > 0) {
      refresh(startMoney, finalMoney);
    } else {
      mPage = 0;
      mUnit = unit;
      mStartMoney = startMoney / unit;
      mTextList = new ArrayList<>();
      mTotalTextList = new ArrayList<>();
      mFinalMoney = finalMoney;
      for (int i = 0; i < (finalMoney - startMoney) / (unit * 10) + 1; i++) {
        if (i < 30 || (finalMoney - startMoney) / (unit * 10) + 1 < 40) {//当前显示刻度数
          mTextList.add(String.valueOf(i * unit * 10 + startMoney));
        }
        mTotalTextList.add(String.valueOf(i * unit * 10 + startMoney));//总共刻度数
      }
      mLastInstance = ((finalMoney - startMoney) % (unit * 10)) / 100 * mScaleMargin;//余数刻度
      if (mTotalTextList.size() < 40) {
        mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin + mLastInstance;
      } else {
        mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin;
      }
      mListener = listener;
      postInvalidate();
    }
  }

  /**
   * 判断更新当前页
   *
   * @param type 0左滑  1右滑
   * @return 是否处理了滚动到对应页
   */
  private boolean notifyDataChanged(int type) {
    if (mTotalTextList.size() < 40) {
      return false;
    }
    if (type == 0) {//向前移动
      if ((mTotalTextList.size() - 20) / 10 - 1 == 0) {
        return false;
      }
      if (mPage < (mTotalTextList.size() - 20) / 10 - 1) {
        mPage++;
        if (mPage == (mTotalTextList.size() - 20) / 10 - 1) {
          if (mTotalTextList.size() >= mPage * 10 + 30) {
            mTextList.clear();
            for (int i = mPage * 10; i < mTotalTextList.size(); i++) {
              mTextList.add(mTotalTextList.get(i));
            }
            mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin + mLastInstance;
            mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(),
                -mScroller.getFinalX() + mScaleMargin * 10 * 10 - 10 * mScaleMargin,
                mScroller.getFinalY());
            return true;
          }
        } else {
          if (mTotalTextList.size() >= mPage * 10 + 30) {
            mTextList.clear();
            for (int i = mPage * 10; i < mPage * 10 + 30; i++) {
              mTextList.add(mTotalTextList.get(i));
            }
            mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin;
            mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(),
                -mScroller.getFinalX() + mScaleMargin * 10 * 10 - 10 * mScaleMargin,
                mScroller.getFinalY());
            return true;
          }
        }
      }
    } else {//向后移动
      if (mPage > 0) {
        mPage--;
        if (mTotalTextList.size() > mPage * 10 + 30) {
          mTextList.clear();
          for (int i = mPage * 10; i < mPage * 10 + 30; i++) {
            mTextList.add(mTotalTextList.get(i));
          }
          mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin;
          mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(),
              mScaleMargin * 10 * 10, mScroller.getFinalY());
          return true;
        }
      }
    }
    return false;
  }

  /**
   * 刷新操作
   */
  public void refresh(final int startMoney, final int finalMoney) {
    new Thread(new Runnable() {//防止数据量过多导致阻塞主线程
      @Override
      public void run() {
        mPage = 0;
        mStartMoney = startMoney / mUnit;
        mFinalMoney = finalMoney;
        mTextList.clear();
        mTotalTextList.clear();
        for (int i = 0; i < (finalMoney - startMoney) / (mUnit * 10) + 1; i++) {
          if (i < 30 || (finalMoney - startMoney) / (mUnit * 10) + 1 < 40) {//当前显示刻度数
            mTextList.add(String.valueOf(i * mUnit * 10 + startMoney));
          }
          mTotalTextList.add(String.valueOf(i * mUnit * 10 + startMoney));//总共刻度数
        }
        mLastInstance = ((finalMoney - startMoney) % (mUnit * 10)) / 100 * mScaleMargin;//余数刻度
        if (mTotalTextList.size() < 40) {
          mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin + mLastInstance;
        } else {
          mScaleWidth = (mTextList.size() * 10 - 10) * mScaleMargin;
        }
        new Handler(Looper.getMainLooper()).post(new Runnable() {
          @Override
          public void run() {
            postInvalidate();
          }
        });
      }
    });
  }

  /**
   * 设置每个刻度间距
   *
   * @param margin 间距
   * @return 返回当前view 链式编程
   */
  public ScrollDividingRuleView setScaleMargin(int margin) {
    mScaleMargin = margin;
    mRectHeight = (int) (mLineHeight + mTextSize + mTextLineMargin + 20);
    return this;
  }

  /**
   * 设置文字和刻度线的间距
   */
  public ScrollDividingRuleView setTextLineMargin(int textLineMargin) {
    mTextLineMargin = textLineMargin;
    mRectHeight = (int) (mLineHeight + mTextSize + mTextLineMargin + 20);
    return this;
  }

  /**
   * 设置文字和刻度线的间距(滚动有问题)
   */
  public ScrollDividingRuleView setInitPosition(int position) {
    mInitPosition=position;
    return this;
  }
}

调用代码:

final ScrollDividingRuleView scrollDividingRuleView = findViewById(R.id.scroll_dividing_rule_view);
    editText = findViewById(R.id.edit_text);
    scrollDividingRuleView.bindMoneyData(0, 100000, 100,
        new ScrollDividingRuleView.OnScrollListener() {
          @Override public void onScaleScrollChanged(long scale) {
            isDividingScroll=true;//防止滚动的时候和edittext绑定数据出问题
            editText.setText(String.valueOf(scale*100));
            editText.setSelection(String.valueOf(scale*100).length());
          }
        });

    scrollDividingRuleView.setScaleMargin(30)
        .setTextLineMargin(30);

    editText.addTextChangedListener(new TextWatcher() {
      @Override
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
      }

      @Override
      public void onTextChanged(CharSequence s, int start, int before, int count) {
      }

      @Override
      public void afterTextChanged(Editable s) {
        String money = s.toString().trim();
        if (money.length() > 0 && !TextUtils.isEmpty(money)) {
          float i = Float.parseFloat(money);
            if (!isDividingScroll) {
              scrollDividingRuleView.setNowScale(i >= 100? i / 100 : 0);
            } else {
              isDividingScroll = false;
            }
          }
      }
    });

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zqb.scrolldividingrule.MainActivity">
    <com.zqb.scrolldividingrule.ScrollDividingRuleView
        android:layout_marginTop="50px"
        android:id="@+id/scroll_dividing_rule_view"
        android:layout_width="match_parent"
        android:layout_marginLeft="30px"
        android:layout_marginRight="30px"
        android:layout_height="wrap_content"
        app:line_height="80px"
        app:dividing_text_size="30px"/>
    <View
        android:layout_marginTop="90px"
        android:background="@color/colorAccent"
        android:layout_centerHorizontal="true"
        android:layout_width="1px"
        android:layout_height="120px"/>
    <EditText
        android:inputType="number"
        android:id="@+id/edit_text"
        android:layout_marginLeft="10dp"
        android:textSize="25sp"
        android:layout_marginTop="300px"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

下载地址

如果显示30个有卡顿的话,可以自己调整。
注:paint.setStyle(Paint.Style.FILL); 防止画出来的文字带空心
paint.measureText(“123”); 获取文字显示的宽度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值