Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[分享]一种无需设置ViewHolder的ListView写法 #99

Open
licheedev opened this issue Mar 21, 2015 · 14 comments
Open

[分享]一种无需设置ViewHolder的ListView写法 #99

licheedev opened this issue Mar 21, 2015 · 14 comments

Comments

@licheedev
Copy link

http://www.bignerdranch.com/blog/customizing-android-listview-rows-subclassing/

@licheedev
Copy link
Author

顺便转到博客上面了
http://www.licheetec.com/2015/03/21/no-viewholder-listview/

@jinsen47
Copy link

已收藏~

@liujingyuan1
Copy link

实现下!看看!

@midiao
Copy link

midiao commented Mar 22, 2015

我按照http://www.bignerdranch.com/blog/customizing-android-listview-rows-subclassing/
这个博客上自己实现了一遍,结果报错,错误提示出错的是以下这行
ItemView itemView = (ItemView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false);

Logcat:
android.view.InflateException: Binary XML file line #1: Error inflating class com.jikexueyuan.noviewholder.ItemView
at android.view.LayoutInflater.createView(LayoutInflater.java:603)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:696)
at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.jikexueyuan.noviewholder.ItemView.inflate(ItemView.java:20)
at com.jikexueyuan.noviewholder.ItemAdapter.getView(ItemAdapter.java:22)
at android.widget.AbsListView.obtainView(AbsListView.java:2255)
at android.widget.ListView.makeAndAddView(ListView.java:1790)
at android.widget.ListView.fillDown(ListView.java:691)
at android.widget.ListView.fillFromTop(ListView.java:752)
at android.widget.ListView.layoutChildren(ListView.java:1630)
at android.widget.AbsListView.onLayout(AbsListView.java:2087)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:502)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1983)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1740)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NoSuchMethodException: [class android.content.Context, interface android.util.AttributeSet]
at java.lang.Class.getConstructorOrMethod(Class.java:472)
at java.lang.Class.getConstructor(Class.java:446)
at android.view.LayoutInflater.createView(LayoutInflater.java:568)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:696)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
            at com.jikexueyuan.noviewholder.ItemView.inflate(ItemView.java:20)
            at com.jikexueyuan.noviewholder.ItemAdapter.getView(ItemAdapter.java:22)
            at android.widget.AbsListView.obtainView(AbsListView.java:2255)
            at android.widget.ListView.makeAndAddView(ListView.java:1790)
            at android.widget.ListView.fillDown(ListView.java:691)
            at android.widget.ListView.fillFromTop(ListView.java:752)
            at android.widget.ListView.layoutChildren(ListView.java:1630)
            at android.widget.AbsListView.onLayout(AbsListView.java:2087)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:502)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
            at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
            at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:14817)
            at android.view.ViewGroup.layout(ViewGroup.java:4631)
            at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1983)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1740)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
            at android.view.Choreographer.doCallbacks(Choreographer.java:574)
            at android.view.Choreographer.doFrame(Choreographer.java:544)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5001)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

@licheetec 能不能抽空看一下,因为实在太疑惑了。

@licheedev
Copy link
Author

@midiao 把你的代码发下。

@midiao
Copy link

midiao commented Mar 22, 2015

Item.java

public class Item {
private int mImageId;
private String mTitle;
private String mDescription;
public int getmImageId() {
    return mImageId;
}
public void setmImageId(int mImageId) {
    this.mImageId = mImageId;
}
public String getmTitle() {
    return mTitle;
}
public void setmTitle(String mTitle) {
    this.mTitle = mTitle;
}
public String getmDescription() {
    return mDescription;
}
public void setmDescription(String mDescription) {
    this.mDescription = mDescription;
}
}

ItemAdapter.java

public class ItemAdapter extends ArrayAdapter<Item>{
public ItemAdapter(Context c, List<Item> items){
    super(c, 0, items);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ItemView itemView = (ItemView)convertView;
    if (itemView == null)
        itemView = ItemView.inflate(parent);
    itemView.setItem(getItem(position));
    return itemView;
}
}

ItemView.java

public class ItemView extends RelativeLayout {
private TextView mTitleTextView;
private TextView mDescriptionTextView;
private ImageView mImageView;

public static ItemView inflate(ViewGroup parent) {
    ItemView itemView = (ItemView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false);
    return itemView;
}

public ItemView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    LayoutInflater.from(context).inflate(R.layout.item_view_children, this, true);
    setupChildren();
}

private void setupChildren() {
    mTitleTextView = (TextView) findViewById(R.id.item_titleTextView);
    mDescriptionTextView = (TextView) findViewById(R.id.item_descriptionTextView);
    mImageView = (ImageView) findViewById(R.id.item_imageView);
}

public void setItem(Item item) {
    mTitleTextView.setText(item.getmTitle());
    mDescriptionTextView.setText(item.getmDescription());
    mImageView.setImageResource(item.getmImageId());
}
}

MainActivity.java

public class MainActivity extends ActionBarActivity {
private ListView listView;
private List<Item> itemList;
private ItemAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();
}

private void init(){
    listView = (ListView) findViewById(R.id.listView);
    initData();
    adapter = new ItemAdapter(this, itemList);
    listView.setAdapter(adapter);

}
private void initData(){
    itemList = new ArrayList<>();
    for (int i = 0; i < 100; i++){
        Item item = new Item();
        item.setmImageId(i % 2 == 0 ? R.drawable.img_cat : R.drawable.img_doge);
        item.setmTitle("Title No." + i);
        item.setmDescription("The content of No." + i);
        itemList.add(item);
    }
}
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></ListView>
</RelativeLayout>

item.view.xml

<com.jikexueyuan.noviewholder.ItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
</com.jikexueyuan.noviewholder.ItemView>

item.view.children.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
    android:id="@+id/item_imageView"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_margin="5dp"
    android:background="@android:color/darker_gray"
    android:contentDescription="啦啦啦" />

<TextView
    android:id="@+id/item_titleTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@id/item_imageView"
    android:text="title text" />

<TextView
    android:id="@+id/item_descriptionTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/item_titleTextView"
    android:layout_marginTop="5dp"
    android:layout_toRightOf="@id/item_imageView"
    android:text="description text" />
</merge>

@licheetec

@licheedev
Copy link
Author

@midiao 看最后一个有原因的异常信息(Caused by的那个)

Caused by: java.lang.NoSuchMethodException: [class android.content.Context, interface android.util.AttributeSet]

应该是填充xml初始化ItemView的时候,调用的是参数为(Context context, AttributeSet attrs)的构造方法,而你只声明了参数为(Context context, AttributeSet attrs, int defStyle)的这个,改成

    public ItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.item_view_children, this,
                true);
        setupChildren();
    }

应该就可以了

@midiao
Copy link

midiao commented Mar 22, 2015

按你说的修改之后,确实可以了,不过想问问

public ItemView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);  
    LayoutInflater.from(context).inflate(R.layout.item_view_children, this, true);
    setupChildren();
}
public ItemView(Context context, AttributeSet attrs) {
    super(context, attrs);
    LayoutInflater.from(context).inflate(R.layout.item_view_children, this, true);
    setupChildren();
}

这两种构造方法有什么区别呢?(有三个参数的那个构造方法被我注释掉之后,程序依然可以运行;但是如果把两个参数的那个构造方法注释掉程序就会报错),这是为什么呢?
@licheetec

@licheedev
Copy link
Author

@midiao 这篇文章有详细说明。

http://www.cnblogs.com/angeldevil/p/3479431.html

@midiao
Copy link

midiao commented Mar 22, 2015

受教了,非常感谢指导。
@licheetec

@jorry
Copy link

jorry commented Mar 23, 2015

这种实现的原理是什么?

@licheedev
Copy link
Author

@jorry 原理其实很简单,就是通过自定义View的方式,把ViewHolder的职能转移到该自定义View上,直接可以在自定义View中缓存子View的引用,而不用经过ViewHolder做中间人。

@liuchenx
Copy link

liuchenx commented Apr 1, 2015

这个写法相当与把setTag的缓存 放到itemview 内存进行全局存储, 我不觉得这个效率比settag高 不过
至于原文作者不喜欢的几个原因, 可以这么写, 也是一个老外写的, 原文找不到了, 用的稀疏数组

public class ViewHolder {
    public static  T get(View view, int id) {
        SparseArray viewHolder = (SparseArray) view.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray();
            view.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (T) childView;
    }
}

上面这个是工具类
下面是getView代码

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.adapter_listactivity_base_test, null);
    }

    ImageView iv = ViewHolder.get(convertView, R.id.image);
    TextView tv = ViewHolder.get(convertView, R.id.text);

    // to do ...
    // iv.setImageBitmap();
        //  tv.setText();
    return convertView;
}

效率没有实测, 应该差不多, 取决与稀疏数组的查找

@GcsSloop
Copy link

看这里,万能适配器:http://www.imooc.com/learn/372

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants