Coder Social home page Coder Social logo

booheeruler's Introduction

BooheeRuler

  这是仿写薄荷健康里面体重选择尺的控件,因为最新的0.1.x版本经历重大更新(重构,把尺子分成多种形式),所以重新写一个README来介绍(旧的README在这里):

最初是为了参加HenCoder的活动,最后也获奖了,谢谢HenCoder凯哥!

介绍

  由于不少伙伴们发邮件或者issue让我做纵向的尺子,其实我也很想做,但是最近项目有点忙,最后就只能在不太忙的阶段偷偷把它做出来了,欢迎大家使用,多多指教小弟哈。   这次更新主要是把InnerRuler里面的一些公共的逻辑提取出来,做成抽象类,然后再由两个HorizontalRuler和VerticalRuler继承它,再实现一些公共逻辑的处理,最后实现四个类型的子类:LeftHeadRuler、TopHeadRuler、RightHeadRuler、BottomHeadRuler。**这样能大大减少重复的代码,缺点就是逻辑分开了,想通过代码看实现一个尺子的逻辑就要跳来跳去有点不方便。具体的重构思路在这

要是想参考下一个完整的尺子的实现逻辑,可以看上一个版本0.0.7的代码,直接在Gradle里面: compile 'com.yanzhikai:BooheeRuler:0.0.7'就可以了。   

使用

Gradle

    compile 'com.yanzhikai:BooheeRuler:0.1.6'

使用方法

  Demo里面有4个尺子,这里就写一个,因为都是差不多的。BooheeRuler分成两个部分,一个是上面的显示多少kg的数字Layout,还有一个就是下面的尺子。首先在xml文件里调用(下面属性的作用可以看后面的表格):

        <!--数字和单位-->
        <yanzhikai.ruler.KgNumberLayout
            android:id="@+id/knl_bottom_head"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:kgTextSize="20sp"
            app:scaleTextSize="50sp" />                          
                                                           
    <!--尺子-->
    <yanzhikai.ruler.BooheeRuler
        android:id="@+id/br_top_head"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        app:bigScaleLength="40dp"
        app:bigScaleWidth="2.5dp"
        app:count="10"
        app:currentScale="666"
        app:cursorDrawable="@drawable/cursor_shape"
        app:cursorHeight="45dp"
        app:cursorWidth="4dp"
        app:maxScale="2000"
        app:minScale="464"
        app:numberTextSize="22sp"
        app:paddingStartAndEnd="10dp"
        app:rulerStyle="TOP_HEAD"
        app:scaleInterval="11.5dp"
        app:smallScaleLength="20dp"
        app:smallScaleWidth="1.5dp"
        app:textMarginHead="80dp" />                                                       
                                                          

  然后在java代码里面使用:

public class MainActivity extends AppCompatActivity {
    private BooheeRuler br_top_head;
    private KgNumberLayout knl_top_head;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        br_top_head = (BooheeRuler) findViewById(R.id.br_top_head);
        knl_top_head = (KgNumberLayout) findViewById(R.id.knl_top_head);
        knl_top_head.bindRuler(br_top_head);

    }
}

BooheeRuler的使用环境是API >= 16

属性

BooheeRuler的属性

属性名称 意义 类型 默认值
minScale 尺子的最小刻度值 integer 464,在尺子上显示就是 464 * factor46.4
maxScale 尺子的最大刻度值 integer 2000,在尺子上显示就是200 * factor 200.0
smallScaleLength 尺子小刻度(0.1)的刻度线长度 dimension 30px
smallScaleWidth 尺子小刻度(0.1)的刻度线宽度/粗细 dimension 3px
bigScaleLength 尺子大刻度(1.0)的刻度线长度 dimension 60px
bigScaleWidth 尺子大刻度(1.0)的刻度线宽度/粗细 dimension 5px
cursorHeight 尺子中间的选定光标的刻度图高度 dimension 70px
cursorWidth 尺子中间的选定光标的刻度图宽度 dimension 8px
textMarginTop textMarginHead 尺子数字文字距离边界距离 dimension 120px
numberTextSize 尺子数字文字大小 dimension 28px
scaleInterval 尺子每条刻度线之间的距离 dimension 18px
numberTextColor 尺子数字文字颜色 color #2B2E2B
scaleColor 尺子刻度线的颜色 color #e2e5e2
currentScale 尺子初始选定刻度 float (maxScale + minScale)/2
cursorDrawable 尺子中间选定光标的Drawable(会把drawable伸缩到设定的宽高上) dimension @drawable/cursor_shape
count 一个大刻度格子里面的小刻度格子数 integer 10
paddingStartAndEnd 控制尺子两端的padding dimension 0
canEdgeEffect (new) 是否启用边缘效果 boolean true
edgeColor (new) 边缘效果的颜色(API大于等于21设置才有效) color #4bbb74
rulerBackGround 尺子的背景 reference或者color #f6f9f6
factor(new) 乘积因子,尺子刻度值实际显示为:scale * factor float 0.1
rulerStyle 尺子的形态(下面有具体介绍) enum TOP_HEAD

  接下来是选择尺子形态的属性rulerStyle:

rulerStyle选项 意义
TOP_HEAD 头部向上的尺子
BOTTOM_HEAD 头部向下的尺子
LEFT_HEAD 头部向左的尺子
RIGHT_HEAD 头部向右的尺子

如效果图中,对应位置的尺子的rulerStyle为:上——BOTTOM_HEAD,下——TOP_HEAD,左——RIGHT_HEAD,右——LEFT_HEAD。

  使用setXX()方法改变一些尺子属性的时候,请调用refreshRuler()方法让尺子重新初始化,不然数值可能会错乱,出现一堆bug。除了setCurrentScale(),这个不用刷新,会直接执行跳转。

factor说明

  相当于设置每个刻度的最小单位。如factor等于5时:

  注意,回调中的scale值还是原来的currentScale,所以在KgNumberLayout里面做的处理改成:

    @Override
    public void onScaleChanging(float scale) {
        if (mRuler != null) {
            tv_scale.setText(RulerStringUtil.resultValueOf(scale, mRuler.getFactor()));
        }
    }

边缘效果说明

  0.1.2版本新增的效果,在API大于等于21(Android5.0)的时候是这样的效果:

  在API小于21(Android5.0)的时候是这样的效果:

KgNumberLayout属性

属性名称 意义 类型 默认值
scaleTextSize 数字字体大小 dimension 80
kgTextSize 单位字体大小 dimension 40
scaleTextColor 数字字体颜色 color #4bbb74
kgTextColor 单位字体颜色 color #4bbb74
kgUnitText 单位文字内容 string kg

接口

  除了使用KgNumberLayout作为显示BooheeRuler的当前刻度之外,还可以通过实现RulerCallback回调接口来获取当前选定刻度:

public interface RulerCallback {
    //选取刻度变化的时候回调
    //注意,scale值不一定是实际值,大概值是实际值/factor
    void onScaleChanging(float scale);
}

  实现了这个接口之后,再调用BooheeRuler.setCallback(RulerCallback rulerCallback)方法传入即可。

PS:由于0.1.4更新了factor属性,所以推荐使用RulerStringUtil.resultValueOf(scale, mRuler.getFactor())方法来将scale属性转化为String,具体可以参考KgNumberLayout的源码。

更新

  • 2017/10/23 version 0.0.5:
    • 修改了画刻度的方法,改为只画当前屏幕显示的刻度
    • 增加了count属性,用于设置一个大刻度格子里面的小刻度格子数,默认是10
  • 2017/10/30 version 0.0.6:
    • 加入VelocityTracker的回收处理(之前只是clear并没有recycle),提高性能。
    • 加入属性paddingStartAndEnd,用于控制尺子两端的padding。
    • 让刻度的绘制前后多半大格,这样可以提前显示出下面的数字,让过渡不会变得那么突兀。
    • 取消了一些不必要的log输出。

非常感谢JulianAndroid为我指出来VelocityTracker这个错误。

  • 2017/10/30 version 0.0.7:

    • 之前VelocityTracker重复使用了addMovement,现在取消掉了。
  • 2017/11/15 version 0.1.0:

    • 重构代码,将尺子分为4个形态。
    • 对细节有一些小改动:如背景设置换成以InnerRuler为主体,优化Padding等。
  • 2017/11/16 version 0.1.1:

    • 修复KgNumberLayout修改单位会出错的bug
  • 2017/11/28 version 0.1.2:

    • 修复触发ACTION_CANCEL事件会令刻度停在非整点地方的bug
    • 增加边缘效果
  • 2017/12/13 version 0.1.3:

    • 性能优化:BooheeRuler的onLayout之前没有利用change属性,导致每次刷新都会重新Layout,现在不会。
    • 性能优化:修改了画刻度的逻辑,现在在刻度范围很大的情况下还可以顺畅滑动。
    • 修复了goToScale()重复回调导致显示刻度不准确的问题。

非常感谢littlezan提出的性能优化建议。

  • 2017/12/25 version 0.1.4:
    • 功能增加:增加属性factor,乘积因子,可以调节刻度值具体最小单位,默认值是0.1。如currentScale是464时,显示出的值为464 * 0.1 = 46.4。增加了RulerStringUtil类来处理回调方法的scale值。
  • 2017/12/28 version 0.1.5:
    • 修复使用setXX()方法改变一些尺子属性的时候,会导致各种错乱的情况。措施:请使用完setXX()方法改变属性之后,调用refreshRuler()方法让尺子重新初始化。(除了setCurrentScale(),这个不用刷新,会直接执行跳转,之前忘记说了,以为就算刷新了也没什么问题,不好意思。。。)
  • 2019/1/1 version 0.1.6:
    • 没想到隔了一年才更新,可怕。修复了因为刻度转px值float转int丢失的精度可能会导致无限绘制的问题。

非常感谢levianye发现Bug并提出修复建议和PR。 非常感谢madongqiang2201发现Bug并提出修复建议。

开源协议

  BooheeRuler遵循MIT协议。

关于作者

id:炎之铠

炎之铠的邮箱:[email protected]

CSDN:http://blog.csdn.net/totond

booheeruler's People

Contributors

totond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

booheeruler's Issues

一些建议

有一些APP需要设置标尺的横向和纵向,另外 公英制 的尺度是不一样的,可以考虑加一下。个人建议,仅供参考。

关于刻度尺最小单位的问题

请问如果我想自定义最小单位该如何去做?例如我想让每个刻度最小单位为10,滑动一个刻度就是增加10或者减少10。并且考虑到多种应用场景,我想让显示的数字不带小数点,全部显示整数,这个该如何处理?最近项目用到大神的框架,希望能指点一下

mCurrentScale的值可能会引起的一个无限绘制的问题

您好,在浏览了您的代码之后,我发现一个可能存在的问题,如题:

如果说mCurrentScale 无限趋近其近似整数的时候,理论上将会一直循环执行scrollBackToCurrentScale()方法

例如 mCurrentScale = 10.991211……,mCurrentScale!=Math.round(mCurrentScale),这个不等式将永远会成立,我觉得是不是应该再加一个处理精度的方法,防止无限绘制下去。

如果刻度尺在scrollview这种布局里面滑动会很卡

如果刻度尺在被scrollview这样的布局包裹的情况下,滑动会有卡顿的感觉,建议在onTouchEvent里面加上处理,我这里重写处理后感觉非常流畅,建议您可以加上,这里是我的处理方式

public boolean onTouchEvent(MotionEvent event) {
ViewGroup parent = (ViewGroup) getParent();
float currentX = event.getX();
//开始速度检测
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            parent.requestDisallowInterceptTouchEvent(true);
            if (!mOverScroller.isFinished()) {
                mOverScroller.abortAnimation();
            }

            mLastX = currentX;
            break;
        case MotionEvent.ACTION_MOVE:
            float moveX = mLastX - currentX;
            mLastX = currentX;
            scrollBy((int) (moveX), 0);
            break;
        case MotionEvent.ACTION_UP:
            //处理松手后的Fling
            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int velocityX = (int) mVelocityTracker.getXVelocity();
            if (Math.abs(velocityX) > mMinimumVelocity) {
                fling(-velocityX);
            } else {
                scrollBackToCurrentScale();
            }
            //VelocityTracker回收
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            releaseEdgeEffects();
            parent.requestDisallowInterceptTouchEvent(false);
            break;
        case MotionEvent.ACTION_CANCEL:
            if (!mOverScroller.isFinished()) {
                mOverScroller.abortAnimation();
            }
            //回滚到整点刻度
            scrollBackToCurrentScale();
            //VelocityTracker回收
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            releaseEdgeEffects();
            parent.requestDisallowInterceptTouchEvent(false);
            break;
    }
    return true;
}

主要就是加上requestDisallowInterceptTouchEvent解决垂直和水平的滑动冲突即可
最后非常感谢作者开源的这个控件,写的非常棒,结构清晰,可读性非常棒,帮了很大的忙,真的非常感谢!!

drawScale方法

请问 drawScale方法中if (locationX > getScrollX() - mDrawOffset && locationX < (getScrollX() + canvas.getWidth() + mDrawOffset))
判断什么的?

轮廓线没有画出来

应该是InnerRuler 这个类里面mOutLinePaint没有设置setAntiAlias(true),所以轮廓线没有画出来,建议加这个,或者添加一个属性可以设置轮廓线的宽度的,超过setStrokeWidth超过1的应该也能显示出来

InnerRuler中没有回收mVelocityTracker

我在看HorizontalScrollView的时候,看到onTouchEvent中MotionEvent.ACTION_UP,都会回收mVelocityTracker:

case MotionEvent.ACTION_UP:
        //...
        recycleVelocityTracker();
        // ...
    break;

光标线

其实可以使用getScrollX() + width 来算出绘制光标或横线的位置。

一些疑问

locationX是从0开始的,为什么绘制出来却在屏幕中间呀。

or (int i = mMinScale; i <= mMaxScale; i++){
            float locationX = (i - mMinScale) * mInterval;
            if (i % 10 == 0) {
                canvas.drawLine(locationX, 0, locationX, mBigScaleLength, mBigScalePaint);
                canvas.drawText(String.valueOf(i/10), locationX, 4 * mSmallScaleLength , mTextPaint);
            }else {
                canvas.drawLine(locationX, 0, locationX, mSmallScaleLength, mSmallScalePaint);
            }
        }

请教一下

你在 HorizontalRuler 的scrollBackToCurrentScale 和 fling 方法中调用了 invalidate()方法,为什么它的子类的ondraw()方法没有调用呢?

关于Scroller.fling的请教

你好。我在模仿薄荷卷尺的时候有一个问题:
调用Scroller.fling滑到最后一段距离有一个明显的减速过程,但是我发现你的和薄荷的都没有这样。
我的滑动处理的代码是下面这样的

  int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:

                if (!mFlingScroller.isFinished()) {
                    mFlingScroller.abortAnimation();
                }
                int index = event.getActionIndex();
                mPointActivtyId = event.getPointerId(index);
                mLastX = event.getX();
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                } else {
                    mVelocityTracker.clear();
                }
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                int pointIndex = event.findPointerIndex(mPointActivtyId);
                if (pointIndex == -1) {
                    Log.i(TAG, "onTouchEvent: error index");
                    break;
                }

                float x = event.getX(pointIndex);
                float dx = x - mLastX;
                float scrollDx = -dx;

                scrollBy((int) scrollDx, 0);

                mLastX = x;

                break;
            case MotionEvent.ACTION_UP:
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
                float xvel = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
                        mPointActivtyId);
                Log.i(TAG, "onTouchEvent: " + xvel);

                if (Math.abs(xvel) > mMinimumFling) {
                    mFlingScroller.fling(
                            getScrollX(), getScrollY(),
                            -(int) xvel, 0,//数据设为计算出的速度的相反值
                            leftMaxScorll, rightMaxScroll,
                            0, 0);
                    invalidate();
                } else {
                    ajustScrollX();
                }


                mPointActivtyId = -1;
                mLastX = -1;


                break;
            case MotionEvent.ACTION_CANCEL:
                mPointActivtyId = -1;
                mLastX = -1;
                mVelocityTracker.recycle();
                break;
        }

建议

希望能添加个两边虚化的功能

服了

定义了这么多的属性

除0

//把滑动偏移量scrollX转化为刻度Scale
private float scrollXtoScale(int scrollX) {
return ((float) (scrollX - mMinPosition) / mLength) * mMaxLength + mParent.getMinScale();
}

这个函数会产生 除0 情况。mLength可能为0

询问Fling结束时的处理方式

我在读你的代码时,感觉你对Fling结束时的处理方式很巧妙。恕我孤陋寡闻,是你自己想的还是有什么参考依据?
下面这段代码:

@Override
public void computeScroll() {
    if (mOverScroller.computeScrollOffset()) {
        scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY());

        // 用这种方式判断Fling结束时刻,我觉得挺巧妙的
        if (!mOverScroller.computeScrollOffset() && mCurrentScale != Math.round(mCurrentScale)){
            //Fling完进行一次检测回滚
            scrollBackToCurrentScale();
        }
        invalidate();
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.