SoFunction
Updated on 2025-04-10

Android realizes scrolling scale effect

origin

Recently, I am helping someone make a pedometer, which involves the collection of height, weight and other information; I have referred to the implementation of many apps and feel that the sliding scale in "Le Dynamics" is more elegant. So, the app was decompiled and found that it was implemented in the form of pictures, that is, a picture with scale is embedded in the ScrollView.
I personally think that this method is too inflexible and has a lot of dependence on artists, so I want to customize a scale control.

Requirements Analysis

  1. Draw the scale to distinguish between integer and normal scale
  2. The red pointer is always in the middle of the scale, indicating the current scale
  3. The maximum and minimum values ​​of the scale can be set dynamically
  4. The height or width of the scale can be set, and the middle scale remains unchanged after setting.
  5. Sliding, the current scale changes after sliding

Knowledge points involved

  1. View mechanism
  2. canvas drawing
  3. Use of Scroller tool class
  4. Custom View properties
  5. Click and slide events handling

Final effect

Since gifs cannot be embedded in Jianshu, it will not affect the effect, please move to github to view them. If you think it is good, please give me a star ^_^/LichFaker/ScaleView

Implementation process

1. Create a new class: HorizontalScaleScrollView, inherited from View

2. Get custom attributes in the constructor:

protected void init(AttributeSet attrs) {  
 // Get custom properties TypedArray ta = getContext().obtainStyledAttributes(attrs, ATTR);  
 mMin = (LF_SCALE_MIN, 0);  
 mMax = (LF_SCALE_MAX, 200);  
 mScaleMargin = (LF_SCALE_MARGIN, 15);  
 mScaleHeight = (LF_SCALE_HEIGHT, 20);  
 ();  
 mScroller = new Scroller(getContext());  
}

3. Rewrite onMeasure and calculate the intermediate scale

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int height=(mRectHeight, MeasureSpec.AT_MOST);  
 (widthMeasureSpec, height);    
 mScaleScrollViewRange = getMeasuredWidth();  
 mTempScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin;  
 mMidCountScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin;
}

4. Rewrite onDraw, draw scales and pointers

protected void onDrawScale(Canvas canvas, Paint paint) {  
 (mRectHeight / 4);
 for (int i = 0, k = mMin; i <= mMax - mMin; i++) {
   if (i % 10 == 0) { 
     //The whole value     (i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleMaxHeight, paint); 
     //Integral text     ((k), i * mScaleMargin, mRectHeight - mScaleMaxHeight - 20, paint);
     k += 10;
   } else {
     (i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleHeight, paint); 
   }
 }
}
protected void onDrawPointer(Canvas canvas, Paint paint) {
 ();
 //Number of each screen scale/2 int countScale = mScaleScrollViewRange / mScaleMargin / 2;
 // Calculate the position of the pointer based on the sliding distance [the pointer is always in the middle of the screen] int finalX = ();
 //Sliding scale int tmpCountScale = (int) ((double) finalX / (double) mScaleMargin);//Round and round //Total scale mCountScale = tmpCountScale + countScale + mMin;
 if (mScrollListener != null) { //Callback method   (mCountScale);
 }
 (countScale * mScaleMargin + finalX, mRectHeight,
     countScale * mScaleMargin + finalX, mRectHeight - mScaleMaxHeight - mScaleHeight, paint);
}

Handle sliding events

  1. Record the current x coordinate (for horizontal scale) when the finger is pressed.
  2. During the sliding process of fingers, determine whether the scale pointed by the current pointer has exceeded the boundary. If it exceeds, sliding is prohibited and the current interface is refreshed.
  3. Correct the current scale when your finger is raised.
@Override
public boolean onTouchEvent(MotionEvent event) {
  int x = (int) ();
  switch (()) {
    case MotionEvent.ACTION_DOWN:
      if (mScroller != null && !()) {
        ();
      }
      mScrollLastX = x;
      return true;
    case MotionEvent.ACTION_MOVE:
      int dataX = mScrollLastX - x;
      if (mCountScale - mTempScale < 0) { //Swipe to the right        if (mCountScale <= mMin && dataX <= 0) //Not to continue swiping to the right          return (event);
      } else if (mCountScale - mTempScale > 0) { //Swipe to the left        if (mCountScale >= mMax && dataX >= 0) //Not to continue sliding to the left          return (event);
      }
      smoothScrollBy(dataX, 0);
      mScrollLastX = x;
      postInvalidate();
      mTempScale = mCountScale;
      return true;
    case MotionEvent.ACTION_UP:
      if (mCountScale < mMin) mCountScale = mMin;
      if (mCountScale > mMax) mCountScale = mMax;
      int finalX = (mCountScale - mMidCountScale) * mScaleMargin;
      (finalX); //Correction of pointer position      postInvalidate();
      return true;
  }
  return (event);
}

Final explanation

The above is only for the implementation of horizontal sliding scales. The vertical sliding principle is consistent and has been implemented in the source code. There are also many incomplete aspects, such as:

  1. When you slide quickly for the first time, you can go beyond the boundary, but not afterwards;
  2. Open custom attributes are not enough (depending on the situation);
  3. You can consider completing the horizontal and vertical implementation in one class, because during the implementation process, it is found that many codes are actually similar, but the properties of individual parameters are different. In the coordinate system, vertical can be regarded as being rotated horizontally by 90°, and you can try it in this direction if you have time.