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