RecyclerChart动态属性图标联动数据动态加载详解

2023-03-02 20:03:11 动态 详解 联动

正文

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

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

图表联动

类似于股票的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的变化等。

以上就是RecyclerChart动态属性图标联动数据动态加载详解的详细内容,更多关于RecyclerChart动态属性的资料请关注其它相关文章!

相关文章