RecyclerChart动态属性(一)

2022-12-30  本文已影响0人  _Jun

本章节不再介绍具体的某种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

上一篇下一篇

猜你喜欢

热点阅读