自绘view实现自定义recyclerView的展示

先上图,调用过程看起来极其复杂。


类之间的调用关系

类虽然很多,但是按照功能作用可以分成几个模块

  • 负责给RecyclerView传递数据的Adaptor
  • 负责展示的View
  • 负责传包装绘制信息的Canvas类

关于ViewHolder的疑惑

在使用RecyclerView无可避免的要使用Adapter来进行数据的存放,而Adapter是有固定写法的,一般需要onCreateViewHolder来创建ViewHolder和OnBindViewHolder来绑定数据,但是我发现在我学习的项目中,这两个方法的参数和数据的绑定有所区别,所以,adapter到底是根据什么来绑定要显示的item的呢?

  • 标准版:

    @Override
    public RecyclerView.ViewHolder onCreateMyViewHolder(ViewGroup parent, int viewType) {
        Item1Binding binding = DataBindingUtil.inflate(inflater,R.layout.item_1,parent,false);
        return new ViewHolder(binding);
    }
    @Override
    public void onBindMyViewHolder(RecyclerView.ViewHolder holder, int position) {
      Fruit fruitBean =  list.get(position);
      ((ViewHolder) holder).getBinding().setFruit(fruitBean);
      ((ViewHolder) holder).getBinding().executePendingBindings(); //解决databinding闪烁问题
    }
    class ViewHolder extends RecyclerView.ViewHolder {
    
      private ItemListBinding binding;
    
      public ItemListBinding getBinding() {
          return binding;
      }
    
      public ViewHolder(@NonNull ItemListBinding binding) {
          super(binding.getRoot());
          this.binding = binding;
      }
    }
    
  • 项目版

    //子类分别实现
    @Override
     public ViewModelHolder onCreateViewHolder(ViewGroup parent, int innerViewType)
     {
        return new ViewModelHolder(TVViewModelFactory.create(parent, innerViewType));
     }
     //父类统一实现
      @Override
    public void onBindViewHolder(ViewModelHolder holder, int position, List<Object> payloads)
    {
        super.onBindViewHolder(holder, position, payloads);
        if (holder.getAsyncState() != TvViewHolder.ASYNC_STATE_SUCC)
        {
           updateData(position, getItem(position), holder.getViewModel());
        }
        final TVLifecycleOwner source = getTVLifecycleOwner();
        if (source != null)
        {
            holder.getViewModel().onBind(source);
        }
        else
        {
            mModelGroup.add(holder.getViewModel());
        }
    }
    

项目中的思想是,统一实现一个专用于ViewModel的Adapter,在这些Adapter中使用的都是ViewModel,所以传入ViewHolder的是ViewModel。
其实可以发现,无论在onCreateViewHolder中传入的参数是DataBinding还是ViewModel,只要在自定义的ViewHolder的构造方法中把要显示的View传进就可以了,这可以在源码中发现,

  • 源码

    public ViewHolder(View itemView) {
          if (itemView == null) {
              throw new IllegalArgumentException("itemView may not be null");
          }
          this.itemView = itemView;
      }
    
  • 当参数为ViewModel时自定义的ViewHolder调用的super

    //子类
    public ViewModelHolder(@NonNull TVViewModel viewModel)
    {
       super(viewModel.getRootView());
       mViewModel = viewModel;
    }
    //父类
    public TvViewHolder(View itemView)
    {
        super(itemView);
    }
    
  • 当参数为DataBinding时的super

    public ViewHolder(@NonNull ItemListBinding binding) {
         super(binding.getRoot());
         this.binding = binding;
     }
    

其实发现,无论自定义的ViewHolder传入的参数是什么类型的,调用父类的构造方法的时候,传入的都是view。所以只要获取的参数中的view传给父类构造方法,也就完成了绑定。

Adaptor部分

RowItemAdapter继承于AnsyncListVMAdapter继承于ViewModelAdapter,ViewModelAdapter是所有使用ViewModel的Adapter的父类,onBindViewHolder在这个类中实现,统一进行数据绑定,
RowItemAdapter和其他子类中实现了onCreateViewHoler方法,用于每个不同的子类调用工厂以创建出不同的符合条件的ViewModel,这两个方法在上面已经贴出,不展示了。但是onCreateViewHoler用于构建合适的ViewModel的标志innerViewType是从何而来呢,从同一个类的如下方法中返回,

@Override
public int getItemViewType(int position)
{
    final RowItem item = getItem(position);
    return item == null ? ViewType._VIEW_TYPE_EMPTY : item.mInnerViewType;
}

实际上传入Adapter的数据是元素为RowItem的Array,而这个RowItem是一个抽象类,所以Array中的数据是存放了一系列实现了特定抽象方法的对象,看看这个RowItem类

public abstract class RowItem
{
   public final int mInnerViewType;

   public RowItem(int innerViewType)
   {
      this.mInnerViewType = innerViewType;
    }

    public abstract void updateViewData(@NonNull TVViewModel model);
 }

可以看到实现这个类中有mInnerViewType变量,所以,数组中每一个元组都有这个值,根据position获取到特定位置的元素,即可获取到type从而创建ViewModel,在下面即将介绍的CanvasRowItem就必须要继承于RowItem,才能作为数据集被传到Adapter中,在RowItem中有一个updateViewData方法,也就是子类要实现的抽象方法,用于进行数据更新。

自绘View--Canvas部分

  • CanvasNode绘制信息类,提供了用于绘制各种情况下的绘制方法,
    举两个例子,实际上对于不同状态只是返回一个含有不同状态变量的对象

    public static <T extends BaseCanvas> CanvasNode<T> focused(@NonNull CanvasBuilder<T> canvas)
    {
         return new CanvasNode<>(CanvasState.VIEW_FOCUSED, canvas, null);
     }
    public static <T extends BaseCanvas> CanvasNode<T> normal(@NonNull CanvasBuilder<T> canvas, @NonNull CanvasRefresher<T> refresher)
    {
      return new CanvasNode<>(CanvasState.NONE, canvas, refresher);
    }
    //构造方法,传入state
    private CanvasNode(int state, @NonNull CanvasBuilder<T> builder, @Nullable CanvasRefresher<T> refresher)
    {
        mState = state;
        mCanvasBuilder = builder;
        mRefresher = refresher;
    }
    
  • CanvasBundle容器类,用于封装CanvasNode的第一层容器

  • CanvasBundleExt工厂类,用于创建各种用途的CanvasBundle,例如按钮类,文本类,

     public static CanvasBundle createItem(long hash, String logoUrl, String content)
    {
      final int width = 541;
      final int height = 80;
      return new CanvasBundle(hash, hash, width, height, Arrays.asList(
              // 背景
              CanvasNode.focused((context, bundle) -> buildViewBg(R.drawable.common_view_bg_normal, width, height, context)),
              // 文字
              CanvasNode.focused((context, bundle) -> {
                  final TextCanvas title = new TextCanvas();
                  title.setDesignTextSize(32);
                  title.setText(content);
                  title.setTextColor(ContextCompat.getColor(context, R.color.ui_color_white_100));
                  title.setMaxLines(1);
                  title.setMaxDesignWidth(width - 65 - 30);
                  title.setEllipsize(TextUtils.TruncateAt.MARQUEE);
                  title.setMarqueeRepeatLimit(TextCanvas.MARQUEE_REPEAT_FOREVER);
                  final int textHeight = title.getTextDesignHeight();
                  title.setDesignRect(
                          65,
                          (height - textHeight) >> 1,
                          width - 30,
                          (height + textHeight) >> 1);
                  title.getTextDesignHeight(); // 消除dirty
                  return title;
              }),
              CanvasNode.normal((context, bundle) -> {
                  final TextCanvas title = new TextCanvas();
                  title.setDesignTextSize(32);
                  title.setText(content);
                  title.setTextColor(ContextCompat.getColor(context, R.color.ui_color_white_80));
                  title.setMaxLines(1);
                  title.setMaxDesignWidth(width - 65 - 30);
                  final int textHeight = title.getTextDesignHeight();
                  title.setDesignRect(
                          65,
                          (height - textHeight) >> 1,
                          width - 30,
                          (height + textHeight) >> 1);
                  title.getTextDesignHeight(); // 消除dirty
                  return title;
              }),
              CanvasNode.focused((context, bundle) -> buildLightAnim(width, height, context)).disableExternalAlpha()
      )).setFocusScale(1.05f).setTopMargin(8).setBottomMargin(8);
    }
    

实际上就是把所有的CanvasNode作为元素存储在CanvasBundle的数组变量中,

  • CanvasViewModel用于获取实际显示的View,在onCreateViewHolder中调用,

     @Override
    public void initView(@NonNull ViewGroup parent)
    {
       mView = new CanvasView(parent.getContext());
       mView.setLayoutParams(new  GridLayoutManager.LayoutParams(GridLayoutManager.LayoutParams.WRAP_CONTENT, GridLayoutManager.LayoutParams.WRAP_CONTENT));
       setRootView(mView);
    }
     @Override
    public void updateViewData(@NonNull CanvasBundle data)
    {
        super.updateViewData(data);
        mView.setCanvasBundle(data);
        setItemInfo(data.mItemInfo);
        mRunnable = data.mRunnable;
        setFocusScale(data.mFocusScale);
     } 
    

View部分

CanvasView继承自SpecifySizeView继承自View

  • CanvasView 在CanvasViewModel的初始化过程中被实例化,并在CanvasViewModel的updataViewData中注入CanvasBundle,

    @Override
    protected void onDrawNormal(Canvas canvas)
    {
      if (mBundle != null && mBundle.mPivotX != Integer.MIN_VALUE)
      {
          setPivotX(mBundle.mPivotX);
      }
      else
      {
          setPivotX(getWidth() >> 1);
      }
      if (mBundle != null && mBundle.mPivotY != Integer.MIN_VALUE)
      {
          setPivotY(mBundle.mPivotY);
      }
      else
      {
          setPivotY(getHeight() >> 1);
      }
      if (mBundle != null)
      {
          mDrawingRecord.clear();
          if (mBundle.onDraw(getCanvasState(), canvas, mDrawingRecord))
          {
              removeCallbacks(mInvalidateRunnable);
          }
          else
          {
              postDelayed(mInvalidateRunnable, 500/*ms*/);
          }
      }
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容