【问题标题】:Why Is My Custom Android ArrayAdapter Not Refreshing Its ListView When New Data Is Added To The Array?为什么我的自定义 Android ArrayAdapter 在向数组添加新数据时不刷新其 ListView?
【发布时间】:2016-04-13 16:11:08
【问题描述】:

作为我的 Android 应用程序的一部分,我正在集成一个非常简单的自定义群聊功能;这只是一个概念验证功能。我知道这是暂时的,每当我重新启动我的应用程序时,我的聊天就会消失。我的应用使用 Fragments 和 ViewPager 在我的应用中创建标签。

我支持此功能的是一个 LinkedList,其中包含消息对象(发送时间、发件人姓名、消息)。每当发送或接收聊天时,它都会被添加到 LinkedList。在 MainActivity.java 的开头,我声明了列表

public static LinkedList<Message> chatList;

在onCreate方法中,我初始化列表

chatList = new LinkedList<Message>();

我有一个在后台运行的侦听器线程,用于侦听传入的消息。我在它的构造函数中将 chatList 传递给了这个线程

MyListener myListener = new MyListener(MainActivity.this, chatList);
Thread listenerThread = new Thread(myListener);
listenerThread.start();

每当监听线程接收到消息时,它都会将其粘贴到 LinkedList 中

chatList.add(new_message_object);

为了显示聊天,我使用了一个由自定义 ArrayAdapter 支持的 ListView,我在其中重写了 getView() 方法。 ArrayAdapter 从我的 LinkedList 的 toArray() 方法中获取数组,并在屏幕上显示聊天。

这个过程几乎有效。每当收到聊天记录时,LinkedList 就会成功填充。问题是让 ListView 立即更新并显示聊天。如果我在我的应用程序中切换到一个新的片段/选项卡,然后切换回我的聊天选项卡,那么列表就会被填充;但是,每当我想看到任何新聊天时,我都必须这样做。

对于我在本地输入的任何聊天(即在我的聊天功能中)也是如此。它仍然添加到 LinkedList,但 ListView 没有刷新。

我不想使用 SwipeRefreshLayout,我宁愿更新列表本身。我在自定义 ArrayAdapter 中创建了一个方法

public void refreshList(){
    this.notifyDataSetChanged();
}

每当有新消息对象添加到 LinkedList 时,我都会调用它,但它不会刷新列表。

那么,我做错了什么?就像我说的,我更喜欢 ListView 更新本身,就像一个适当的聊天程序一样。

谢谢

编辑:

根据要求,这是我的自定义 ArrayAdapter

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    Object[] objects;

    public CustomArrayAdapter(Context context, int resource, Object[] objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        this.objects = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        MessageHolder messageHolder = null;

        if(row == null){
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(resource, parent, false);
            messageHolder = new MessageHolder();
            messageHolder.chat_information = (TextView) row.findViewById(R.id.chat_information);
            messageHolder.chat_message = (TextView) row.findViewById(R.id.chat_message);

            row.setTag(messageHolder);
        } else {
            messageHolder = (MessageHolder) row.getTag();
        }

        Message item = (Message) objects[position];
        messageHolder.chat_information.setText(item.getSenderName() + Constants.NEWLINE + item.getSendTime());
        messageHolder.chat_message.setText(item.getMessageText());

        return row;
    }

    public void refreshList(){
        this.notifyDataSetChanged();
    }

    private class MessageHolder {
        TextView chat_information;
        TextView chat_message;
    }
}

编辑 2:

我正在添加我的 Chat.java 片段。

public class Chat extends Fragment {

    private View rootView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.chat, container, false);
        return rootView;
    }

    @Override
    public void onViewCreated(View rootView, Bundle savedInstanceState) {
        super.onViewCreated(rootView, savedInstanceState);
        displayChats();
    }

    @Override
    public View getView() {
        Button sendChatButton = (Button) rootView.findViewById(R.id.text_send);
        sendChatButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Date rightNow = new Date();
                SimpleDateFormat timeSDF = new SimpleDateFormat(Constants.SIMPLE_TIME);
                SimpleDateFormat dateSDF = new SimpleDateFormat(Constants.SIMPLE_DATE);
                SharedPreferences myAppPreferences = getContext().getSharedPreferences(Constants.PREFS_NAME, getContext().MODE_PRIVATE);
                EditText chatEntryWindow = (EditText)rootView.findViewById(R.id.chat_text_compose);
                String message = chatEntryWindow.getText().toString();
                String username = myAppPreferences.getString("username", Constants.TABLET_ID);
                Message myMessage = new Message(username, message, 0, dateSDF.format(rightNow), timeSDF.format(rightNow));
                CustomArrayAdapter caa = new CustomArrayAdapter(getActivity(), R.layout.outgoing_line_of_chat, MainActivity.chatList);
                caa.add(myMessage);
                caa.notifyDataSetChanged();
                new SendChat(getActivity(), message, username).execute();
            }
        });
        return super.getView();
    }

    public void displayChats(){
        ListView list = (ListView) rootView.findViewById(R.id.chat_text_display);
        ArrayAdapter adapter = new CustomArrayAdapter(getActivity(), R.layout.outgoing_line_of_chat, MainActivity.chatList);
        list.setAdapter(adapter);
    }
}

【问题讨论】:

  • “ArrayAdapter 读取我的 LinkedList 的 toArray() 方法并在屏幕上显示聊天”——这对我来说没有多大意义。我建议您编辑您的问题并发布您的getView() 方法,或者可能是整个ArrayAdapter。通常,您在ArrayAdapter 上调用add()/insert()/remove() 来更新内容并刷新附加的AdapterView,但notifyDataSetChanged()(在直接操作List 之后)应该有效果一样。
  • @CommonsWare 这意味着 ArrayAdapter 需要一个数组,而 LinkedList.toArray() 方法将 LinkedList 转换为 ArrayAdapter 可用的数组。
  • 我怀疑你有更大的问题。我仍然建议您编辑您的问题并发布您的ArrayAdapter 子类。
  • @CommonsWare 我已将我的 ArrayAdapter 添加到帖子中。
  • 我已经更新了我的答案。

标签: android listview android-arrayadapter


【解决方案1】:

在数组适配器中插入新数据后使用以下内容:

yourAdapterName.notifyDataSetChanged();

【讨论】:

  • 它没有。我尝试在插入新数据后调用 notifyDataSetChanged,但似乎没有帮助。
【解决方案2】:

您的CustomArrayAdapter 不使用您的LinkedList。它使用Object[]。我假设,根据您的问题和 cmets,当您创建 CustomArrayAdapter 时,您在 LinkedList 上调用 toArray() 以获取您的 Object[]

不过,此时Object[]LinkedList 完全分离。假设一开始,LinkedList 有 5 个Message 对象。你调用toArray(),你会得到一个包含这5个Message对象的Object[5]。您稍后在LinkedList 上调用add(),以添加第6 个MessageObject[5] 仍然是 Object[5] 并且对第 6 个 Message 一无所知。 AdapterView 更新的唯一方法是完全替换 CustomArrayAdapter

所以,摆脱Object[]ArrayAdapter 在其一半的构造函数中采用 List。所以,替换:

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    Object[] objects;

    public CustomArrayAdapter(Context context, int resource, Object[] objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        this.objects = objects;
    }

与:

public class CustomArrayAdapter extends ArrayAdapter<Message> {

    Context context;
    int resource;

    public CustomArrayAdapter(Context context, int resource, List<Message> messages) {
        super(context, resource, messages);
        this.context = context;
        this.resource = resource;
    }

然后,在getView() 中,将Message item = (Message) objects[position]; 替换为Message item=getItem(position);

最后,当新的Message 到达时,在CustomArrayAdapter 上调用add(),这将更新您的List&lt;Message&gt; 并更新附加的AdapterView

【讨论】:

  • 这适用于我在本地发送的聊天,但不适用于我从外部收到的聊天;我仍然必须退出,然后再回到聊天标签。另外,现在我收到 IllegalStateException 错误“适配器的内容已更改,但 listview 没有收到通知。确保适配器的内容不是从后台线程修改的,而只是从 UI 线程修改的”。就像我说的,我正在使用后台线程来接收来自外部的消息。
  • @Brian:您需要在主应用程序线程的ArrayAdapter 上调用add()。如何实现这在很大程度上取决于应用的架构以及后台线程如何将这些 Message 对象传递到主应用线程(例如事件总线)。
  • 这样做需要大量的重写。我需要使用后台线程来接收消息,该线程与主应用程序线程断开连接。就像我说的,我的 LinkedList 是在我的主线程中创建的,并在其构造函数中传递给我的后台线程。必须有一种更简单的方法来做到这一点。
  • @Brian:“这样做需要大量重写”——好吧,仅在主应用程序线程上更新 UI 是 Android 中的一个基本概念。这与您的CustomArrayAdapter 无关。自 Android 1.0 发布之前,这一要求已经存在了 10 年的大部分时间。
  • “我不允许使用任何这样的外部库”——LocalBroadcastManager 是 Android 支持包的一部分,并且在您的 SDK 中。除此之外,任何对你施加限制的人都是白痴。
【解决方案3】:

像这样更改代码。这样不会做太多改动,

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    List objects ;
    Activity activity ;

    public CustomArrayAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        //clone the arraylist.
        this.objects = new ArrayList(objects);
        activity = (Activity) context;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        MessageHolder messageHolder = null;

        if (row == null) {
            LayoutInflater inflater = LayoutInflater.from(context);
            row = inflater.inflate(resource, parent, false);
            messageHolder = new MessageHolder();
            messageHolder.chat_information = (TextView) row.findViewById(R.id.chat_information);
            messageHolder.chat_message = (TextView) row.findViewById(R.id.chat_message);

            row.setTag(messageHolder);
        } else {
            messageHolder = (MessageHolder) row.getTag();
        }

        Message item = (Message) objects.get(position);
        messageHolder.chat_information.setText(item.getSenderName() + Constants.NEWLINE + item.getSendTime());
        messageHolder.chat_message.setText(item.getMessageText());

        return row;
    }

    public void setObjects(List newObjects)
    {
        if (objects != null)
        {
            objects.clear();
            objects.addAll(newObjects);
        }
    }


    public void refreshList() {
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });

    }

    private class MessageHolder {
        TextView chat_information;
        TextView chat_message;
    }
}

还有每条聊天消息调用setObjects()方法和链表调用refreshList()方法

【讨论】:

  • 我收到错误“java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()”。
  • 通过在构造函数中传递 Looper.getMainLooper() 来初始化处理程序。 handler = new Handler(Looper.getMainLooper());
  • 现在有什么问题?
  • 我对代码做了一点改动。检查更新的代码
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多