美文网首页
RecyclerChart动态属性(一)

RecyclerChart动态属性(一)

作者: _Jun | 来源:发表于2022-12-30 14:53 被阅读0次

    本章节不再介绍具体的某种Chart如何绘制,RecyclerChart系列除了第一章节的概括性的介绍外,其它基本上每章节介绍一种Chart的绘制逻辑,具体的实现方式也比较固定,基本上只需要实现对应的Render,将Chart的绘制逻辑实现即可,不同的Chart的绘制逻辑会因为chart 的方式不同而不同的绘制逻辑常见的Barchart、LineChart、BezierChart等,还有一些特制的一些Chart的绘制;其中一些Chart涉及一些边界以及绘制细节问题的处理,是这些图表的难点所在。

    之上的这些章节介绍的内容,笔者暂且给它们定性为静态的图表绘制,就是RecyclerView滑动停下来时展现的用户眼前的数据展现,Adapter其中的一段visibleCount对应的具体的item, 借用ItemDecoration绘制每个Chart在每个Item的内容展现。本章节将讲述一些RecyclerChart中笔者归结为动态属性,比如高亮选中Item时类似Top Poupwindow的窗口显示展示的值;左右滑动无限加载RecyclerChart的数据;滑动过程中松手后展示单位时间内(天、周、月)等的数据时,Chart回弹效果等。

    高亮选中

    选中分单击选中、长按选中,这个属性设置在Entry 中,所有Chart的数据Entry都extends Entry.

    public class Entry extends BaseEntry implements Parcelable {
        public static final int TYPE_UNSELECTED = 0;//没有选中
        public static final int TYPE_SINGLE_TAP_UP_SELECTED = 1;//单击选中
        public static final int TYPE_LONG_PRESS_SELECTED = 2;//长按选中
        public int isSelected = TYPE_UNSELECTED;
        public boolean isSelected() {
            return isSelected == TYPE_SINGLE_TAP_UP_SELECTED || isSelected == TYPE_LONG_PRESS_SELECTED;
        }
        ......
    }
    

    要处理Recyclerview中每个Item的onClick, onLongClick 事件, 首先需要让RecyclerView addOnItemTouchListener OnItemTouchListener接口, 笔者这里定义了一个类RecyclerItemGestureListener 实现了OnItemTouchListener 接口。

    OnItemTouchListener 中原本包含三个方法,最原始的可以自己实现它的onTouchEvent 方法实现点击、长按效果。

    笔者这里通过GestureDetector来代替实现,这样就只需要将onClick 跟 onLongClick时的业务逻辑实现在GestureDetector 对应的方法里,GestureDetector如何接管 OnItemTouchListener的事件呢?首先RecyclerItemGestureListener implements RecyclerView.OnItemTouchListener, 然后在 RecyclerItemGestureListener 的构造方法里create GestureDetector 实例, 在onInterceptTouchEvent方法里接管拦截即可,不再需要实现 其它两方法。

    onInterceptTouchEvent的实现,找到当前Touch到的childView, mGestureDetector.onTouchEvent(e) 为true时作为其中一个条件作为返回:

    @Override
    public boolean onInterceptTouchEvent(@NonNull RecyclerView view, @NonNull MotionEvent e) {
      View childView = view.findChildViewUnder(e.getX(), e.getY());
      if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
        mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
        return true;
      }
      return false;
    }
    

    接下来我们看如何处理GestureDetector的实现逻辑,GestureDetector原本的构造函数如下:

    public GestureDetector(@UiContext Context context, OnGestureListener listener) {
         this(context, listener, null);
    }
    

    其中参数二 OnGestureListener 是一个需要实现接口的变量:

    public interface OnGestureListener {
    
      boolean onDown(MotionEvent e);
    
      void onShowPress(MotionEvent e);
    
      boolean onSingleTapUp(MotionEvent e);
    
      boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
    
      void onLongPress(MotionEvent e);
    
      boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }
    

    这里只需要实现 onSingleTapUp, onLongPress 两个方法,所以笔者这里选择传入 OnGestureListener 的一个Child class SimpleOnGestureListener的 instance作为参数, SimpleOnGestureListener 是 GestureDetector内部的一个 static class, 包含了很多的Gesture相关的方法,这里选着实现 onSingleTapUp, onLongPress两个方法即可。

    上面的铺垫完毕后,进入到正题,处理onSingleTapUp, onLongPress 下选中Item Entry的逻辑。

    onSingleTapUp的业务逻辑如下,看当前是否selectBarEntry为null, 不为null的时候,点击的是否正好是 selectBarEntry等情况的考虑。

    最后将当前选中的BarEntry 通过 自定义的一个Listener(后续会介绍) 将其传递给上层的具体业务层。

    onLongPress的业务逻辑,处理选中跟非选中的逻辑跟 onSingleTapUp大致一样,同样将选中的BarEntry 通过 自定义的一个Listener往上层传。

    在 onLongPress 里这里有个小 trick, 设定了RecyclerItemGestureListener的一个filed 属性 isLongPressing 为true. 以及设定 SpeedRatioLayoutManager 下的 ratioSpeed 的值为0. 这里跟后续解决长按状态下,RecyclerView 无法滑动,只能拖动显示高亮Item的Case(下面会介绍)。

    以上是正常情况下点击、长按对选中高亮的处理,还有一些边界需要处理的,边界1:当Recyclerview 回到快速左右滑动时,需要对原来的selectBarEntry 进行释放。这里需要RecyclerView 实现 OnScrollListener,OnScrollListener的添加在 RecyclerItemGestureListener 的构造方法里添加的。

    边界处理2: 当RecyclerView的滑动在MotionEvent.ACTION_MOVE 的情况下也需要对 selectBarEntry的处理做一个补充,笔者这里定义了一个interface 用来处理在 RecyclerView层面的事件,BaseChartRecyclerView 作为所有ChartRecyclerView的基类。

    同样在RecyclerItemGestureListener 的construct 方法里添加 OnChartTouchListener 接口。在onChartGestureEnd 对 SpeedRatioLayoutManager 下的 ratioSpeed 的值做一个恢复操作(具体作用,下个小节会介绍。)

    @Override
    public void onChartGestureEnd(MotionEvent e) {
      Log.d("OnItemTouch", " onChartGestureEnd: " + System.currentTimeMillis()/1000);
      isLongPressing = false;
      if (null != layoutManager) {//控制RecyclerView的滑动
        layoutManager.resetRatioSpeed();
      }
    }
    

    然后就是onChartGestureMovingOn 即 RecyclerView在 onTouchEvent中的MotionEvent.ACTION_MOVE情况下的一个处理:

    高亮选中,这里是isLongPressing 情况下对selectBarEntry 的处理,并将选中的Entry通过 Listener传出。否则就置空 selectBarEntry, 撤销选中 mListener.onItemSelected(null, -1);

    整个以上的逻辑就是对 selectBarEntry 的设定,然后就是调用 mAdapter.notifyItemChanged(position, false). 然后就是触发RecyclerView的重绘,整个逻辑就到之前的Render 的 drawHighLight 方法了。

    //绘制选中时 highLight 标线及浮框。
    public  <E extends BaseYAxis> void drawHighLight(Canvas canvas, @NonNull RecyclerView parent, E yAxis) {
      if (mBarChartAttrs.enableValueMark) {
        int childCount = parent.getChildCount();
        View child;
        for (int i = 0; i < childCount; i++) {
          child = parent.getChildAt(i);
          T entry = (T) child.getTag();
          RectF rectF = ChartComputeUtil.getBarChartRectF(child, parent, yAxis, mBarChartAttrs, entry);
          float width = child.getWidth();
          float childCenter = child.getLeft() + width / 2;
          String valueStr = mHighLightValueFormatter.getBarLabel(entry);
          if (entry.isSelected() && !TextUtils.isEmpty(valueStr)) {
            int chartColor = getChartColor(entry);
            float rectHeight = drawHighLightValue(canvas, valueStr, childCenter, parent, chartColor);
            float[] points = new float[]{childCenter, rectF.top, childCenter, rectHeight};
            drawHighLightLine(canvas, points, chartColor);
          }
        }
      }
    }
    

    以上过程中提到的将selectBarEntry 通过Listener往上层业务传出,这里在RecyclerItemGestureListener 定义了一个 接口OnItemGestureListener:

    public interface OnItemGestureListener {
    
      void onItemClick(View view, int position);
    
      void onLongItemClick(View view, int position);
    
      void onItemSelected(BarEntry barEntry, int position);
    
      void onScrollStateChanged(RecyclerView recyclerView, int newState);
    
      void onScrolled(RecyclerView recyclerView, int dx, int dy);
    }
    

    然后避免直接实现接口,中间实现一个类 SimpleItemGestureListener, 作为 RecyclerItemGestureListener的参数传入。

    以上逻辑中在LongPress 设定SpeedRatioLayoutManager 下的 ratioSpeed 为0, 以及在 onChartGestureEnd (RecyclerView的MotionEvent.ACTION_UP 以及MotionEvent.ACTION_CANCEL 事件下)对 ratioSpeed的恢复, default value 通过 Attribute传入

    public void resetRatioSpeed(){
        this.ratioSpeed = mAttrs.ratioSpeed;
    }
    
    attrs.ratioSpeed = ta.getFloat(R.styleable.SleepChartRecyclerView_layoutManagerOrientation, 1f);
    

    这里 SpeedRatioLayoutManager 继承自 LinearLayoutManager, ratioSpeed可以用来控制 RecyclerView的滑动的Speed, 当ratioSpeed 为1f的时候默认的滑动,当为0的时候RecyclerView滑不动。

    这样就解决了当处于长按选中时,滑动 跟Recyclerview 滑动冲突的问题解决,跟我们常规的处理滑动冲突不太一样,也没有用到两层View的覆盖情况下的冲突解决手段,却又巧妙地实现了功能需求,类似股票软件的那种长按及滑动效果。

    鉴于本文的篇幅过长,不再介绍其它动态属性功能了。总结一下,高亮选中功能,这里主要涉及到 四个Listener的实现,首先是 实现 RecyclerView.OnItemTouchListener 的 RecyclerItemGestureListener; 然后是 BaseChartRecyclerView 中的 OnChartTouchListener, 功能时将onTouchEvent中的MotionEvent事件向上暴露;第三个是 在 RecyclerItemGestureListener 构造方法内 给 Recyclerview添加的OnScrollListener 匿名内部实现Instance,主要是处理 selectedEntry 快速滑动的释放,并将onScrollStateChanged 接口通过第四个接口向上暴露;第四个就是OnItemGestureListener 自定义接口,以及它的模板实现类SimpleItemGestureListener, 主要功能给上层业务实现例如左右滑加载更多的功能。 附上一张RecyclerLineChart 点击选中、LongPress情况下高亮选中,以及滑动时选中被释放的 gif动图。

    下一篇介绍上面提到的左右滑无限加载数据以及Chart图表动态回弹的效果。

    作者:cxy107750
    链接:https://juejin.cn/post/7182155495027769405

    相关文章

      网友评论

          本文标题:RecyclerChart动态属性(一)

          本文链接:https://www.haomeiwen.com/subject/grwxcdtx.html