本章节继上一章节中相关的动态属性作介绍,主要讲述三个功能完成点,第一个是上下图表联动问题,相似于股票软件K线上面的Candle图跟下边的MACD之类的图表联动的完成;第二点是左右滑动,触边时数据动态加载的完成;第三点当滑动Fling结束后,显现数据跨了日、周、月视图的一个周期时,数据回溯回弹的一个作用,之前IPhone的健康APP完成了相似的作用,最近新的好像已经去除了该作用。

下面的这个能量的图表gif 能够较好地看出联动跟回溯的过程:

RecyclerChart动态属性(二)

图表联动

相似于股票的K线跟底部成交量Barchat图表,这儿也是上下两个Chart图表,笔者在写到这儿的时分,突然间有个大胆的想法,便是完全可以在一个Chart里去绘制上下两部分的数据展现,这样的话也不会存在两个图表联动的问题,同时可能会由于少了一个Chart,功能更好。好了,这儿先讲现在的完成方法。MPAndroidChart中的两个上下两个图表也可以完成连动的方法完成,经过OnChartGestureListener接口完成。由于RecyclerChart是基于Recyclerview 完成的,所以其实只需完成两个Recylcerview的联动即可。

如下在recyclerBarChart 的滑动监听中 同步处理recyclerLineChart的滑动,相同包含回溯的滑动。

recyclerBarChart.addOnScrollListener(new RecyclerView.OnScrollListener() {
 private boolean isRightScrollInner;
 @Override
 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
  super.onScrollStateChanged(recyclerView, newState);
  if (newState == RecyclerView.SCROLL_STATE_IDLE) {
    ....
   //回溯
   if (mBarChartAttrs.enableScrollToScale) {
    int scrollToByDx = ChartComputeUtil.computeScrollByXOffset(recyclerView, displayNumber, getXAxisType());
    recyclerView.scrollBy(scrollToByDx, 0);
    recyclerLineChart.scrollBy(scrollToByDx, 0);
    }
    .....
   }
  }
​
 @Override
 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
  super.onScrolled(recyclerView, dx, dy);
  //判别左滑,右滑时,ScrollView的方位不一样。
  isRightScrollInner = dx < 0;
  if (recyclerBarChart.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
   mItemGestureListener.resetSelectedBarEntry();//清除recyclerLineChart的长按。
   recyclerLineChart.scrollBy(dx, dy);
   }
  }
});

相同的,在线性表 recyclerLineChart 的滑动监听里需求同步处理recyclerBarChart的滑动,代码相似。

数据动态加载

其实相似于分页加载数据,跟纵向vertical加载相似,这儿是横向horizontal处理的,相同在上面的监听的Listener里边处理 当条件 !recyclerView.canScrollHorizontally(-1) 左滑不动,加载数据到左面,这儿LayoutManager由于 reverse的,所以是 DataList.addAll(list);当!recyclerView.canScrollHorizontally(1) 右滑不动是,DataList.addAll(0, list)

@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
 super.onScrollStateChanged(recyclerView, newState);
 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
  //加载更多
  if (!recyclerView.canScrollHorizontally(-1) && isRightScrollInner) {//左滑不动
   loadData(updateUI(start))
   } else if (!recyclerView.canScrollHorizontally(1)) {//右滑不动
   loadData(updateUI(end))
   }
  }
}

回溯

这儿先介绍一下完成计划:笔者在构建Entry的时分埋了一个type的钩子,当Entry归于日周月视图的鸿沟,比方日视图的0点,周视图的周一,月视图的1号,通常情况下是这个Item的左鸿沟;但是当RTL时需求特别处理。

第二,当停下来的时分,遍历当前屏幕显现的Items时,当不对齐的时分,这儿必定存在一个上述说到的特别鸿沟的Item,核算它到Chart鸿沟的(不包含XAis)的间隔,存到一个DistanceCompare的对象中。

第三, 在RecyclerView松手Fling中止的时分,核算上面的DistanceCompare对象中的,distanceLeft、distanceRight; 依据 isNearLeft() 去判别是向左,还是向右回弹,isNearLeft() true 向左,不然向右。

//月线接近左面
public boolean isNearLeft(){
  return distanceLeft < distanceRight;
}
​
//核算 DistanceCompare 
private static <T extends RecyclerBarEntry> DistanceCompare findDisplayFirstTypePosition(RecyclerView recyclerView, int displayNumbers) {
 LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
 DistanceCompare distanceCompare = new DistanceCompare(0, 0);
 BaseBarChartAdapter adapter = (BaseBarChartAdapter) recyclerView.getAdapter();
 if (null == manager || null == adapter) {
  return distanceCompare;
  }
 List<T> entries = adapter.getEntries();
 int firstVisibleItemPosition = manager.findFirstVisibleItemPosition();
 int position = firstVisibleItemPosition; //从右边的第一个View开端找
 int parentRight = recyclerView.getWidth() - recyclerView.getPaddingRight();
 int parentLeft = recyclerView.getPaddingLeft();
​
 for (int i = 0; i < displayNumbers; i++) {
  if (i > 0) {
   position++;
   }
  if (position >= 0 && position < entries.size()) {
   T barEntry = entries.get(position);
   if (barEntry.type == RecyclerBarEntry.TYPE_XAXIS_FIRST || barEntry.type == RecyclerBarEntry.TYPE_XAXIS_SPECIAL) {
    distanceCompare.position = position;
    View positionView = manager.findViewByPosition(position);
    if (null != positionView){
     int viewLeft = positionView.getLeft();
     int viewRight = positionView.getRight();
     distanceCompare.distanceRight = parentRight - viewRight;
     distanceCompare.distanceLeft = viewLeft - parentLeft;
     }
    distanceCompare.setBarEntry(barEntry);
    break;
    }
   }
  }
 return distanceCompare;
}

依据 distanceCompare 核算 scrollByXOffset:简单的数学核算,不过需求细心。

public static <T extends RecyclerBarEntry> int computeScrollByXOffset(RecyclerView recyclerView, int displayNumbers, int type) {
 DistanceCompare distanceCompare = findDisplayFirstTypePosition(recyclerView, displayNumbers);
 LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
 BaseBarChartAdapter adapter = (BaseBarChartAdapter) recyclerView.getAdapter();
 if (null == adapter) {
 return 0;
  }
 List<T> entries = adapter.getEntries();
 int positionCompare = distanceCompare.position;
 //T entry = entries.get(positionCompare);
 View compareView = manager.findViewByPosition(positionCompare);
 if (null == compareView) {
 return 0;
  }
 int compareViewRight = compareView.getRight();
 int compareViewLeft = compareView.getLeft();
​
 int childWidth = compareView.getWidth();
 int parentLeft = recyclerView.getPaddingLeft();
 int parentRight = recyclerView.getWidth() - recyclerView.getPaddingRight();
​
 int scrollByXOffset;
 if (distanceCompare.isNearLeft()) {
 //接近左面,content左移,recyclerView右移,取正。
 //情况 1.
 int distance = AppUtils.isRTLDirection() ? compareViewLeft - parentLeft 
   : compareViewRight - parentLeft;//原始调整间隔
 if (positionCompare < displayNumbers + 1) {
  //防止 positionCompare过大,核算firstViewRight时,int越界
 int firstViewRight = compareViewRight + positionCompare * childWidth;
 int distanceRightBoundary = Math.abs(firstViewRight - parentRight);//右鸿沟
 if (distanceRightBoundary < distance) { //content左移不行,顶到头,用distanceRightBoundary
 distance = distanceRightBoundary;
  }
  }
 scrollByXOffset = distance;
  } else {//接近右边,content右移,recyclerView左移,取负。
 int distance = AppUtils.isRTLDirection()?parentRight - compareViewLeft : 
  parentRight - compareViewRight;//原始调整间隔
 if (entries.size() - positionCompare < displayNumbers) {
 //这个值会为负的。
 int lastViewLeft = compareViewLeft - (entries.size() - 1 - positionCompare) * childWidth;
 int distanceLeftBoundary = Math.abs(parentLeft - lastViewLeft);
  //右边 - 左面,由于 lastViewLeft是负值,实际上是两值相加。
 if (distanceLeftBoundary < distance) {//content右移不行,顶到头,distanceLeftBoundary
 distance = distanceLeftBoundary;
  }
  }
 //记得取负, scrollBy的话f
 scrollByXOffset = distance - 2 * distance;
  }
 return scrollByXOffset;
}

最终也在onScrollStateChanged 完成回溯:

@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
 super.onScrollStateChanged(recyclerView, newState);
 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
   .....
  //回溯
  if (mBarChartAttrs.enableScrollToScale) {
   int scrollToByDx = ChartComputeUtil.computeScrollByXOffset(
    recyclerView, displayNumber, getXAxisType());
   recyclerView.scrollBy(scrollToByDx, 0);
   recyclerLineChart.scrollBy(scrollToByDx, 0);
   }
   ......
  }
}

至此,RecyclerChart相关的动态属性相关介绍完了,另外比方回溯完后,YAXis的改变等。