先不說(shuō)別的, 上圖:

項(xiàng)目中,我們可能會(huì)遇到以上那種情況 ,
顏色,品牌,尺寸,等規(guī)格,每個(gè)規(guī)格里面有長(zhǎng)短不一,大小不一,數(shù)量不一的規(guī)格項(xiàng),如果用GridView,則每一項(xiàng)的個(gè)數(shù), 都會(huì)固定,實(shí)現(xiàn)不了這種錯(cuò)落排版的效果。
<strong>怎么辦?</strong>
<strong>怎么辦?</strong>
<strong>怎么辦?</strong>
于是在github上逛了一圈, 找到了我想要的:
https://github.com/blazsolar/FlowLayout ,
感謝前人們?yōu)槲覀兲峁┑姆奖?/p>
到這里,還沒(méi)有結(jié)束, 下面才是今天要討論的東西。
這個(gè)FlowLayout,一般用法是addView(view),可是,我還要嵌套在一個(gè)listview里面,要在getView里面每次
for(xx : xx) {
View view = View.inflate(xxxx,xxx,xx);
Xx xx = view.findViewById(R.id.xx)
Xx xx = ....;
xx.setText(xx);
...
}
好可怕 。
怎么辦?
android已經(jīng)給我們提供指導(dǎo)方向了, 所以我們?cè)谑褂肔istView,GridView等等多條目控件的時(shí)候,會(huì)寫(xiě)一個(gè)適配器,把數(shù)據(jù)層給分開(kāi),那這個(gè)能加一個(gè)適配器嗎,ok,老思路先分析 :
1.首先我要給FlowLayout加個(gè)setAdapter(ListAdapter adapter);
2.數(shù)據(jù)變動(dòng)我要改動(dòng)Flowlayout里面的view,看看ListView源碼,要給adapter注冊(cè)一個(gè)Observer,類似這樣:
adapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
changeViews();
}
@Override
public void onInvalidated() {
changeViews();
}
});
實(shí)現(xiàn)這個(gè)之后,在adapter的notifyDatasetChange的時(shí)候,onChanged方法就會(huì)回調(diào)
3.利用adapter的getView來(lái)得到賦值好的view,并加到FlowLayout,
4.能不能考慮下復(fù)用view,不能每次removeAllViews()再addView啊。
好了,分析完了,從第一條開(kāi)始上代碼:
private ListAdapter adapter;
public void setAdapter(@NonNull ListAdapter adapter) {
if (adapter == null) throw new NullPointerException("FlowLayout.setAdapter不能傳空參數(shù)");
this.adapter = adapter;
changeViews(); //這個(gè)是用來(lái)給Flowlayout加view的邏輯,先忽略
adapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
changeViews();
}
@Override
public void onInvalidated() {
changeViews();
}
});
}
代碼很簡(jiǎn)單,
1.就是在FlowLayout里面保留一個(gè)adapter,
2.然后從adapter里面詢問(wèn)view,
3.注冊(cè)觀察對(duì)象,得以在adapter.notifyDataSetChanged()的時(shí)候,能感知到。
本身很簡(jiǎn)單,一共就兩個(gè)方法嘛~
下面貼changeViews()方法:
private void changeViews() {
final int count = adapter.getCount();
if (count > 0) {
int childCount = getChildCount();
if (childCount > count) {
removeViews(count, childCount - count);
}
for (int i = 0; i < count; i++) {
final View view = adapter.getView(i, getChildAt(i), this);
if (view.getLayoutParams() == null) {
LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp.rightMargin = childHorizontal;
lp.bottomMargin = childVerticle;
view.setLayoutParams(lp);
}
if(getChildAt(i) != view) {
addView(view);
}
}
}else{
removeAllViews();
}
}
這里稍微麻煩點(diǎn),
為什么呢?因?yàn)榭紤]到我們不能每一次都讓adapter重新創(chuàng)建新view,這樣實(shí)則是一種性能上的浪費(fèi),但是真做到ListView那種級(jí)別的復(fù)用,也是難。所以,我就簡(jiǎn)單點(diǎn)做,能復(fù)用就復(fù)用,不能復(fù)用再創(chuàng)建。
"能復(fù)用就復(fù)用,不能復(fù)用再創(chuàng)建" 怎么體現(xiàn)出來(lái)的呢?
final int count = adapter.getCount(); //先拿到要顯示的view的數(shù)量
if (count > 0) {
//在即將顯示的數(shù)量大于0的情況下 再lookup一下flowlayout當(dāng)前子view的數(shù)量
int childCount = getChildCount();
if (childCount > count) {
//如果說(shuō)flowlayout當(dāng)前的子view數(shù)量多過(guò)要顯示的view數(shù)量,那就刪除幾個(gè),讓其數(shù)量保持一致
removeViews(count, childCount - count);
}
//上面就是我所說(shuō)的‘能復(fù)用就復(fù)用,不能復(fù)用再創(chuàng)建‘
for (int i = 0; i < count; i++) {
//這個(gè)就比較簡(jiǎn)單了, 向adapter要view。
//getChildAt(i) 就是adapter內(nèi)getView中的第二個(gè)參數(shù) convertView,
//如果有就傳過(guò)去 ,沒(méi)有就是null,這時(shí)按正常來(lái)說(shuō),我們會(huì)重新創(chuàng)建一個(gè)view。
final View view = adapter.getView(i, getChildAt(i), this);
if (view.getLayoutParams() == null) {
//好了,這時(shí)呢得到了一個(gè)view,看看有沒(méi)有 布局參數(shù),
//沒(méi)有就給一個(gè),免得FlowLayout自動(dòng)生成一個(gè)的話
//會(huì)match_parent,這樣就不合我們的要求了,
LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//這些就是加加右和下margin,讓view 不會(huì)粘一起,好看一點(diǎn),
//可以做成一個(gè)自定義屬性嘛,從xml里面讀,這個(gè)就不討論了,
lp.rightMargin = childHorizontal;
lp.bottomMargin = childVerticle;
view.setLayoutParams(lp);
}
if(getChildAt(i) != view) {
//這里,為啥要判斷下呢?
//這要說(shuō)到前面的蹩腳的復(fù)用了,
//前面把getChildAt(i)傳給了getView,如果FlowLayout本身就有view,
//那么在getView里面,就只是改變一下text,image等等的數(shù)據(jù),這時(shí)
//getChildAt(i) 和adater返回的view肯定還是同一個(gè)view,所以不用重復(fù)加
//----
//但是如果不一樣,那就是說(shuō),getChildAt(i)就是null , 跟本就沒(méi)有,
addView(view);
}
}
ok,F(xiàn)lowLayout的改造已經(jīng)完工了,
回到第一張圖上去,看看怎么用的吧
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="@dimen/dp10"
android:background="#fff"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="42dp"
android:gravity="center_vertical"
android:textColor="@color/normalText"
android:textSize="@dimen/normal"
/>
<demo.FlowLayout
android:id="@+id/flowLayout"
android:layout_marginTop="@dimen/dp10"
app:childHorizontal="5dp"
app:childVerticle="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
public class SelectGuigeAdapter extends LBaseAdapter<GuigeBean, SelectGuigeAdapter.VH> {
public SelectGuigeAdapter(Context context) {
super(context);
}
@Override
public VH createViewHolder(ViewGroup parent, int position) {
return new VH(View.inflate(getContext(),R.layout.select_guige_list_item,null));
}
@Override
public void bindViewHolder(VH holder, GuigeBean data, int position) {
holder.txtTitle.setText(data.name); //這里顯示的是顏色,品牌等
holder.mAdapter.setDataSource(data.items, true); //這里是具體的規(guī)格項(xiàng)
}
static class VH extends LBaseAdapter.ViewHolder {
private TextView txtTitle;
private FlowLayout flowLayout;
private SelectGuigeItemAdapter mAdapter;
public VH(View convertView) {
super(convertView);
txtTitle = get(R.id.title);
flowLayout = get(R.id.flowLayout);
if (mAdapter == null) {
mAdapter = new SelectGuigeItemAdapter(getContext());
flowLayout.setAdapter(mAdapter);
}
}
}
}
再看FlowLayout的適配器
public class SelectGuigeItemAdapter extends LBaseAdapter<String, LBaseAdapter.ViewHolder> {
public SparseBooleanArray selectMap = new SparseBooleanArray();
public SelectGuigeItemAdapter(Context context) {
super(context);
}
@Override
public ViewHolder createViewHolder(ViewGroup parent, int position) {
return new ViewHolder(View.inflate(getContext(), R.layout.checkedtextview_item,null));
}
@Override
public void bindViewHolder(ViewHolder holder, String data, final int position) {
CheckedTextView txt = holder.get(R.id.text);
txt.setText(data);
txt.setChecked(selectMap.get(position,false));
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectMap.clear();
selectMap.put(position, true);
notifyDataSetChanged();
}
});
}
}
ok,連起來(lái)了, 這個(gè)LBaseAdapter是什么鬼, 大家可以關(guān)注下我的上一遍文章
<a href="http://www.itdecent.cn/p/7b7dbbebfb4f">關(guān)于android中ListView的Adapter如何設(shè)計(jì)能通用的一些看法</a>
嗯,先到這,寫(xiě)的不好,望指正