项目需求讨论-Vlayout来快速构建及扩展复杂界面

大家好,今天又带来了项目中具体遇到的需求。做一个首界面,该首界面有很多功能块,同时这些功能块是动态的,因为登录的人的权限的不同,会显示不同的功能块,因为功能模块的数量不一定,所以当功能块多的时候,整个界面是可以上下滑动的。其实类似有点像淘宝的首界面。如下图所示。

界面

首先我说的不是最优的。可能有更好的解决方式。我这里也只是尝试了新的方式而已。

我先说下我最刚开始想到的最传统的想法:

1.首先因为功能块多的时候,需要界面能够滚动,所以我想到最外面用的ScrollView,然后ScrollView中包含了一个竖向排布的LienarLayout。
然后在放入一个ImageView显示这个顶部图片:


然后需要二个横向的LinearLayout,用来显示这个大的分类标题:

然后再放入二个GridView显示功能模块:

OK。我发现我的首界面写好了之后:

<ScrollView>
    <LinearLayout>
        <ImageView>  <顶部图片>
        <LinearLayout><View/><TextView/><LinearLayout>   <我的服务标题栏>
        <GridView />   <我的服务功能块>
        
        <LinearLayout><View/><TextView/><LinearLayout>   <我的功能标题栏>
        <GridView />   <我的功能功能块>
    <LinearLayout>
<ScrollView/>

1.布局的内容非常之多。维护很不方便
2.定制化功能差了很多,如果我下次想在《我的服务》和《我的功能》大功能分类中,再多加一个《我的售后》,又的去布局中查找相应的位置,然后去去添加新的布局代码,或者是我想删除模块功能了,我还得去布局中找出来,然后去删除它。反正就是很麻烦。
3.当前这个界面还算简单的,毕竟功能块都是以类似九宫格的形式呈现,如果哪天多了个《我的售后》,然后这个《我的售后》不是以这种九宫格的形式呈现,整个界面中有各种各样的布句呈现,管理会变的十分麻烦。


有没有什么好的办法呢。

上面说到过。我们的界面有没有像淘宝的首界面,各种布句杂糅在一起,然后又可以上下滚动,没错,那我就模仿淘宝的首页一样写个不就行了么。

这时候我的思路就变了:整个界面就使用一个RecycleView来完成。

然后里面的不同布局方式使用不同的LayoutManager不就可以了么。当然因为前面讲了我们可以模仿淘宝的首页来写,那我们当然是使用阿里巴巴开源的vlayout

这时候介绍一下我们的主角:vlayout

vlayout is a powerfull LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview.

我们可以看到,vlayout是一个强大的RecycleView的LayoutManager,它可以帮我在RecycleView中呈现多种布局方式。


正式起航:

首先,vlayout的基本使用方法,其他大神写的很多也很好。我也不会浪费时间再写一遍:

请看这篇,基本就能够对Vlayout有所了解及使用了:

重要的事情说三遍!!!大家一定要看一遍再使用。

Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!
Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!
Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

我们回头再来看我们上面的具体的项目需求:

(我会先用VLayout实现一种简单的处理。然后再实现更加通用的处理!一定要看完最后的通用处理哦!)

简单处理:

我们首先整个activity的布局变为了:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/work_recycleview"
    android:background="@android:color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:overScrollMode="never"
    >

</android.support.v7.widget.RecyclerView>

是不是变的干净简洁了!!!

然后我们要使用Vlayout来设置我们RecycleView中的各种布局。

RecycleView workRecycleview = (RecycleView)findViewById(R.id.work_recycleview);
//建立我们的委托LayoutManger
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
workRecycleview.setLayoutManager(layoutManager);
//通过这个layoutManager来管理一系列的LayoutHelper
//所以我们先建立一个List来存放等会要用到的LayoutHelper.
List<LayoutHelper> helperList = new LinkedList<>();
//1.
//因为第一个底部图片就这一项,所以我们就直接使用SingleLayoutHelper
SingleLayoutHelper bannerLayoutHelper = new SingleLayoutHelper();
bannerLayoutHelper.setItemCount(1);
helperList.add(bannerLayoutHelper);

//2.
//因为大标题栏是一个横向的LinearLayout,所以使用LinearLayoutHelper
LinearLayoutHelper personTitleHelper = new LinearLayoutHelper();
personTitleHelper.setItemCount(1);
helperList.add(personTitleHelper);

//3.
//因为功能块目前是九宫格,所以使用的是GridLayoutHelper
GridLayoutHelper personGridHelper = new GridLayoutHelper(3);
personGridHelper.setAutoExpand(false);
personGridHelper.setWeights(new float[]{33, 33, 33});
//设置登录时候获取到的该用户权限下显示的功能数量。
personGridHelper.setItemCount(mPersonFunctions.size());
helperList.add(personGridHelper);

//4.
//同2界面
LinearLayoutHelper companyTitleHelper = new LinearLayoutHelper();
companyTitleHelper.setItemCount(1);
helperList.add(companyTitleHelper);

//5.
//同3界面
GridLayoutHelper companyGridHelper = new GridLayoutHelper(3);
companyGridHelper.setWeights(new float[]{33, 33, 33});
companyGridHelper.setAutoExpand(false);
//设置登录时候获取到的该用户权限下显示的功能数量。
companyGridHelper.setItemCount(mCompanyFunctions.size());
helperList.add(companyGridHelper);

如果我们需要增加新的布局控制。我们只需要添加新的LayoutHelper,按顺序添加到我们的helperList中即可。

目前的LayoutHelper有以下几种:

  • LinearLayoutHelper: 线性布局
  • GridLayoutHelper: Grid布局, 支持横向的colspan
  • FixLayoutHelper: 固定布局,始终在屏幕固定位置显示
  • ScrollFixLayoutHelper: 固定布局,但之后当页面滑动到该图片区域才显示, 可以用来做返回顶部或其他书签等
  • FloatLayoutHelper: 浮动布局,可以固定显示在屏幕上,但用户可以拖拽其位置
  • ColumnLayoutHelper: 栏格布局,都布局在一排,可以配置不同列之间的宽度比值
  • SingleLayoutHelper: 通栏布局,只会显示一个组件View
  • OnePlusNLayoutHelper: 一拖N布局,可以配置1-5个子元素
  • StickyLayoutHelper: stikcy布局, 可以配置吸顶或者吸底
  • StaggeredGridLayoutHelper: 瀑布流布局,可配置间隔高度/宽度

既然用到RecycleView ,那怎么可以没有Adapter呢,上面的准备工作做了一部分后,我们开始写我们的Adapter。

我们这里选择继承了VirtualLayoutAdapter:
我们在构造函数中传入我们二个九宫格功能块对应的List进来。

public class WorkAdapter extends VirtualLayoutAdapter {
    int oneFuncs, twoFuncs;//
    public List<FunctionBean> oneFunctions;
    public List<FunctionBean> twoFunctions;

    public static final int BANNER_VIEW_TYPE = 0;
    public static final int DIVIDER_VIEW_TYPE = 1;
    public static final int FUN_VIEW_TYPE = 2;
    
    public WorkAdapter(@NonNull VirtualLayoutManager layoutManager, List<FunctionBean> oneFunctions, List<FunctionBean> twoFunctions,funcItemOnClickListener listener) {
        super(layoutManager);
        this.oneFunctions = oneFunctions;
        this.twoFunctions = twoFunctions;
        this.listener = listener;

        oneFuncs = oneFunctions.size();
        twoFuncs = twoFunctions.size();
    }


}

我们来分别看Adapter中每个方法具体的复写:

@Override
public int getItemCount() {
    int totalCount = 0;
    List<LayoutHelper> helpers = getLayoutHelpers();
    if (helpers == null) {
        return 0;
    }
    for (int i = 0; i < helpers.size(); i++) {
        totalCount += helpers.get(i).getItemCount();
    }
    return totalCount;
}

我们可以看到在getItemCount()方法中,我们通过遍历了LayoutHelper,分别取每个LayoutHelper中我们刚设置的个数。然后加起来,作为整个RecycleView 的个数。

@Override
public int getItemViewType(int position) {
    if (position == 0) {
        return BANNER_VIEW_TYPE;
    } else if (position == 1 || position == (2 + oneFuncs)) {
        return DIVIDER_VIEW_TYPE;
    } else {
        return FUN_VIEW_TYPE;
    }
}

我们可以看到。我们在getItemViewType方法中肯定position的值,返回不同的type,这样等会在onCreateViewHolder方法中就可以返回不同的ViewHolder了。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case BANNER_VIEW_TYPE:
            return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_banner, parent, false));
        case DIVIDER_VIEW_TYPE:
            return new DividerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_divider, parent, false));
        case FUN_VIEW_TYPE:
            return new FuncViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_func, parent, false));
        default:
            return null;
    }
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof DividerViewHolder) {
        if (position == 1) {
            ((DividerViewHolder) holder).dividerTitle.setText("我的服务");
            ((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
        } else {
            ((DividerViewHolder) holder).dividerTitle.setText("我的功能");
            ((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
        }
    } else if (holder instanceof FuncViewHolder) {
        if(position > 1 && position < 2+ oneFuncs){
          ...
          ...
          ...
          
        }else if(position > 2+ oneFuncs){
          ...
          ...
          ...
        }
    }
}

就这样我们就具体的实现了多个布局的设置,而且当你要再加一个新的也很方便。但是也许你这时候会发现,如果我们的布局很长,有很多九宫格,或者真的像淘宝一样,这个界面有各种功能块。那我们刚写的

@Override
public int getItemViewType(int position) {
    if (position == 0) {
        return BANNER_VIEW_TYPE;
    } else if (position == 1 || position == (2 + oneFuncs)) {
        return DIVIDER_VIEW_TYPE;
    } else {
        return FUN_VIEW_TYPE;
    }
}

这里你判断的position就会很多。你可能就要有很多的if-else 来控制返回不同的type.

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof DividerViewHolder) {
        if (position == 1) {
            ((DividerViewHolder) holder).dividerTitle.setText("我的服务");
            ((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
        } else {
            ((DividerViewHolder) holder).dividerTitle.setText("我的功能");
            ((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
        }
    } else if (holder instanceof FuncViewHolder) {
        if(position > 1 && position < 2+ oneFuncs){
          ...
          ...
          ...
          
        }else if(position > 2+ oneFuncs){
          ...
          ...
          ...
        }
    }
}

然后在onBindViewHolder方法里面也要有很多的if-else,到后面维护又会变的很麻烦,所以这样写相对简单,适合模块不多的情况。



复杂的并且更通用的实现:

我们前面是把很多LayoutHelper加入到了Adapter中,然后RecycleView直接设置该Adapter,
我们这次就不这么做了。而且中间多设置一步,就是先每个LayoutHelper设置一个Adapter,成为<子的Adapter>,然后把这些Adapter再统一放入到一个<总的Adapter>中,再把这个<总的Adapter>赋值给RecycleView即可。

因为这样在<总的Adapter>中,对于每个Helper就可以知道个数,并且ViewHolder等赋值都被分配到了那些<子的Adapter>中处理了。我们不用再if-else的写很多情况了。

我们直接来看这个<总的Adapter> 是如何实现的:

(不过我不会完全很仔细的讲解,代码我也不会贴全部,就贴一些主要的地方,讲主要的部分。)

public class DelegateAdapter extends VirtualLayoutAdapter<RecyclerView.ViewHolder> {

    private int mTotal = 0;
    private int mIndex = 0;
    private SparseArray<Adapter> mItemTypeAry = new SparseArray<>();
    @NonNull
    private final List<Pair<AdapterDataObserver, Adapter>> mAdapters = new ArrayList<>();
    private final SparseArray<Pair<AdapterDataObserver, Adapter>> mIndexAry = new SparseArray<>();
    
}

我们先定义了几个参数,

mIndex用来等于标记各个加入的<子的Adapter>的序号

一个用来存<子的Adapter>的Map(别问我为什么Map类型里面只填了一个Adapter,不是Key-Value? 可以补下SparseArray和ArrayMap的知识了,Android中用来替换HashMap的类。)

一个存放了Pair<AdapterDataObserver, Adapter>的List集合(Pair如果也不知道,也可以去补充下,就简单理解为一个有二个属性的对象,第一个属性是AdapterDataObserver,第二个是Adapter)

一个存放了Pair<AdapterDataObserver, Adapter>的Map集合。

那我们知道肯定要有个方法把这些子的Adapter给加进来:


public void setAdapters(@Nullable List<Adapter> adapters) {
    clear();//把相关的参数都重新置空,这里不写出来了。
    
    if (adapters == null) {
        adapters = Collections.emptyList();
    }

    List<LayoutHelper> helpers = new LinkedList<>();

    mTotal = 0;

    Pair<AdapterDataObserver, Adapter> pair;
    for (Adapter adapter : adapters) {
        // every adapter has an unique index id
        
        //自定义类AdapterDataObserver ,继承于RecyclerView.AdapterDataObserver
        AdapterDataObserver observer = new AdapterDataObserver(mTotal, mIndex++);
        adapter.registerAdapterDataObserver(observer);
       
       //子的Adapter中自定义的方法:onCreateLayoutHelper(),用来返回子的Adapter中的LayoutHelper
        LayoutHelper helper = adapter.onCreateLayoutHelper();

        //而且这些子的Adapter中的LayoutHelper的个数,就是这些子的Adapter的个数
        helper.setItemCount(adapter.getItemCount());
        
        //总数为每个LayoutHelper的个数之和,也就是每个子的Adapter的个数之和
        mTotal += helper.getItemCount();
        helpers.add(helper);
        
        
        pair = Pair.create(observer, adapter);
        //这里的mIndexAry存放了以加入的顺序mIndex为Key的Pair<AdapterObserver,Adapter>
        mIndexAry.put(observer.mIndex, pair);
        //同时mAdapters的List集合中也存放了Pair<AdapterObserver,Adapter>
        mAdapters.add(pair);
    }

    super.setLayoutHelpers(helpers);
}

我们看下AdapterDataObser的部分代码:

  protected class AdapterDataObserver extends RecyclerView.AdapterDataObserver {
    int mStartPosition;
    int mIndex = -1;

    public AdapterDataObserver(int startPosition, int index) {
        this.mStartPosition = startPosition;
        this.mIndex = index;
    }
}

我们可以看到我们刚在new每个AdapterDataObser的时候传入的构造函数参数是mTotal, mIndex++,这样是不是正好每个Adapter中的AdapterDataObserver中的mStartPosition参数就是你的这个Adapter在所有整个RecycleView中的开始的position值。而mIndex又说明了这个AdapterDataObserver是第几个,也就是这个Adapter是所有的<子的Adapter>中的第几个。

@Override
public int getItemCount() {
    return mTotal;
}

总数就是返回上面我们的mTotal参数。

@Override
public int getItemViewType(int position) {
    Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
    if (p == null) {
        return RecyclerView.INVALID_TYPE;
    }

    int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);

    if (subItemType < 0) {
        // negative integer, invalid, just return
        return subItemType;
    }

   if (mHasConsistItemType) {
    mItemTypeAry.put(subItemType, p.second);
    return subItemType;
    }

    int index = p.first.mIndex;

    return (int) getCantor(subItemType, index);
}

我们一步步来看这个比较关键的地方,我们之所以不用我们最刚开始第一次讲的Vlayout使用的方法,就是因为我们的LayoutHelper多了之后,在getItemViewType()方法中返回不同的ViewType需要很多if-else来处理。所以这里我们看他是如何自动处理的。

第一步:
Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
我们看findAdapterByPosition方法的具体实现:

@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {

    //获取我们上面的mAdapter集合,里面存的是Pair<AdapterObserver,Adapter>
    final int count = mAdapters.size();
    if (count == 0) {
        return null;
    }

    int s = 0, e = count - 1, m;
    Pair<AdapterDataObserver, Adapter> rs = null;

    // binary search range
    while (s <= e) {
        m = (s + e) / 2;
        rs = mAdapters.get(m);
        int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;

        if (rs.first.mStartPosition > position) {
            e = m - 1;
        } else if (endPosition < position) {
            s = m + 1;
        } else if (rs.first.mStartPosition <= position && endPosition >= position) {
            break;
        }

        rs = null;
    }

    return rs;
}

通过这个方法的字面意思我们不难理解:通过这个<总的Adapter>返回的item的position,来知道这个position是属于我们存了Adapter集合中的哪个Adapter的。
先获取我们上面已经保存了各个Pair<AdapterObserver,Adapter>的mAdapters集合,然后判断个数,为0就直接返回了。不为0,我们就通过二分法查找的方式来进行查找。我们前面已经在每个AdapterDataObserver中存了相对于的Adapter的起始的Position,我们只需要不停的判断现在传给这个方法的position是在(子的Adapter 的起始position) 与 (子的Adapter 的起始position + 子的Adapter的个数)之间,如果是,就说明是属于这个Adapter,我们就在mAdapters集合中取出相应的Pair<AdapterObserver,Adapter>

第二步:

int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);

if (subItemType < 0) {
    // negative integer, invalid, just return
    return subItemType;
}

if (mHasConsistItemType) {
        mItemTypeAry.put(subItemType, p.second);
        return subItemType;
}

int index = p.first.mIndex;

return (int) getCantor(subItemType, index);

这里的(position - p.first.mStartPosition)其实就是这个<总的Adapter>的处于position的这一项,在这个<子的Adapter>里面的具体的position值。最后通过这个<子的Adapter>的getItemViewType来得到<子的Adapter>的ViewType。这样就自动帮我们判断了在<总的Adapter>中的某个position值的Item的所属的<子的Adapter>的ViewType了。而不用写很多if-else来判断了。

这里又要分二种情况,也就是一个boolean值mHasConsistItemType来控制:

在这个<总的Adapter>构造函数中传入,它的作用是whether sub adapters itemTypes are consistent,就是我们的所有的<子的Adapter>的itemType都是一样的。因为如果你在<子的Adapter>中没有覆写getItemViewType方法的话,默认都是返回0,即:

public int getItemViewType(int position) {
        return 0;
}

我们也知道,RecycleView在运行的时候,执行顺序是:

getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->...

如果我们的mHasConsistItemType设置为true的话:
所以我们如果所有的<子的Adapter>中的要用同一个viewType的话,比如这里是0,我们就在getItemViewType方法中执行mItemTypeAry.put(subItemType, p.second);,这样当前的这个<子的Apdater>就存在了key为0的集合中了,然后我们在onCreateViewHolder方法中通过Adapter adapter = mItemTypeAry.get(viewType);取出来就行了,这时候因为viewType为0,就正好取出来我们刚存的Adapter,然后再进入下一次的getItemViewType的时候,就用新的adapter覆盖了key为0的value值,然后再拿到onCreateViewHolder方法里面使用。

如果我们的mHasConsistItemType设置为false的话:
那这时候就用了另外一种方法,首先,因为子的Adapter默认拿到的ViewType都是0,所以我们用了要设置一个可逆算法,比如A方法和还原的B方法,A方法中我们每次传入viewType和另外一个值(这里选定了上面我们拿到的Pair<AdapterDataObserver, Adapter>中的AdapterDataObserver的index值),因为每个<子的Adapter>的index值不同,所以生成的ViewType也不同,然后我们在onCreateViewHolder方法里面,用还原的B方法,获取到index值,然后通过这个index再找回<子的Adapter>,这时候我们就可以调用<子的Adapter>的onCreateViewHolder方法了。

A方法:

private static long getCantor(long k1, long k2) {
    return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
}

B方法:(具体看onCreateViewHolder方法中)

// reverse Cantor Function
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    // reverse Cantor Function
    int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
    int t = (w * w + w) / 2;

    int index = viewType - t;
    int subItemType = w - index;

    Adapter adapter  = findAdapterByIndex(index);
    if (adapter == null) {
        return null;
    }

    return adapter.onCreateViewHolder(parent, subItemType);
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    Pair<AdapterDataObserver, Adapter> pair = findAdapterByPosition(position);
    if (pair == null) {
        return;
    }

    pair.second.onBindViewHolder(holder, position - pair.first.mStartPosition);
    pair.second.onBindViewHolderWithOffset(holder, position - pair.first.mStartPosition, position);
}

findAdapterByPosition:(也是二分法查找,和上面的findAdapterByIndex方法一样,不介绍了。)

@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {
    final int count = mAdapters.size();
    if (count == 0) {
        return null;
    }

    int s = 0, e = count - 1, m;
    Pair<AdapterDataObserver, Adapter> rs = null;

    // binary search range
    while (s <= e) {
        m = (s + e) / 2;
        rs = mAdapters.get(m);
        int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;

        if (rs.first.mStartPosition > position) {
            e = m - 1;
        } else if (endPosition < position) {
            s = m + 1;
        } else if (rs.first.mStartPosition <= position && endPosition >= position) {
            break;
        }

        rs = null;
    }

    return rs;
}

看了上面我们发现了,最后我们虽然给RecycleView赋值了一个<总的Adapter>,但是实际上的onCreateViewHolder方法和onBindViewHolder方法都是调用了每个具体的<子的Adapter>的。

所以我们最终在我们的Activity中的使用

final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true);
recyclerView.setAdapter(delegateAdapter);
List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
adapters.add(XXXXX);//添加不同的子Adapter.
...
//比如这样:
GridLayoutHelper layoutHelper;
layoutHelper = new GridLayoutHelper(4);
layoutHelper.setMargin(0, 10, 0, 10);
layoutHelper.setHGap(3);
layoutHelper.setAspectRatio(4f);
adapters.add(new ASubAdapter(this, layoutHelper, 8));
...
...
delegateAdapter.setAdapters(adapters);

如果我想新加一个功能块,只要新建一个针对这个功能块的Adapter,然后添加到adapters集合中就可以了。完全不用修改原来的代码。只需要在这个新加的功能块的Adapter中处理即可。

具体的使用第二种方式的代码及DelegateAdapter.java 的源码 可以在GitHub中自行观看vlayout

求别乱喷,求点赞。哈哈。。。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 我已经写了个Demo上传到GitHub上了。大家可以看看。BaseLoadAdapter 大家好,又是新的一期项目...
    青蛙要fly阅读 10,201评论 15 78
  • Tangram是阿里出品、用于快速实现组合布局的框架模型,在手机天猫Android&iOS版 内广泛使用 该框架提...
    wintersweett阅读 3,249评论 0 1
  • RecycleView多种布局显示 1.前言 我们知道ListView多种布局显示用到两个方法一个getItemV...
    TheTwo阅读 7,898评论 15 56
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 今天工作重点: 1. 总共的特征种类归纳; 2. 归纳对于某些Loop,在什么样子的情况下应该是什么特征。 一些启...
    sunny_aday阅读 181评论 0 1