【问题标题】:How to: Add child item to listview in onPreExecute, then change View of item added in onPostExecute如何:在 onPreExecute 中将子项添加到列表视图,然后更改在 onPostExecute 中添加的项的视图
【发布时间】:2014-08-03 01:35:47
【问题描述】:

总结:我使用 ListView 来实现聊天。列表视图中的每个条目都是发送或接收的消息,可以是文本或图像。当发送图像时,asynctask 对象将...

  1. protected void onPreExecute() 向列表视图插入一个条目,它包含正在发送的图像的缩略图和进度微调器。
  2. protected Void doInBackground(String... params) - 将上传图片。
  3. protected void onPostExecute(Void param) - 应将步骤 1 中添加的条目中的进度微调器设置为不可见。

我已经完成了第 3 部分。我不确定如何修改第 1 步中添加的 ListView 条目的进度微调器。请记住,在第 1 步中将条目添加到 ListView 后,还有更多条目(聊天消息)可能已添加到存储列表中使用的数据的数组中。还有其他线程可以向这个数组添加数据。

当我通过 ArrayAdapter 执行 add 时,我不知道它被添加到哪个索引。我确实知道它在数组的末尾,所以我考虑保存从执行 getCount() 获得的索引,但由于有多个线程将数据添加到数组,我相信 add 和 getCount 操作必须是原子的,他们不是!

我可以就如何继续使用一些建议。

阵列适配器:

public class ChatArrayAdapter extends ArrayAdapter<ChatListEntry>{

    private static final String TAG = "Kingfisher";
    private TextView message;                                                   
    private List<ChatListEntry> messages = new ArrayList<ChatListEntry>();      
    private RelativeLayout wrapper;                                                                                         
    private LinearLayout wrapper_text_entry;                            

    static class ViewHolder { 
        public ImageButton btn_showFSImageActivity;
        ImageView imgCaptureThbnail;
    }

    public ChatArrayAdapter(Context context, int textview_resourceID) {
        super(context, textview_resourceID);
    }


    @Override
    public void add(ChatListEntry object) {
        messages.add(object);
        super.add(object);
    }

    public int getCount() {
        return this.messages.size();
    }

    public ChatListEntry getItem(int index) {
        return this.messages.get(index);
    }

    /**
     * Every new item that is added to the ListView gets a new View inflater
     * and loads specified layout. Even if the View/Layout is already loaded. 
     * ie the same type of message is being added to the chat listview.
     * This is easier to code, I dont have to check what the view/layout is
     * currently loaded, but this may not be the most efficient way to do it. 
     */
    public View getView(int position, View convertView, ViewGroup parent) {

        //test - id like to see every time getView is invoked. print to log.
        Log.i(TAG, "getview invoked.");


        View row = convertView;

        final ChatListEntry chatentry = getItem(position);

        LayoutInflater inflater = (LayoutInflater)          this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if(chatentry.msgType.equals("IMG")) {
            row = inflater.inflate(R.layout.list_view_row_img, parent, false);


            if(chatentry.isOutbound == false) {
                chatentry.imgthmbnail = createThumbnailImg(getBitmapFromURL(chatentry.content));

                //test
                Log.i(TAG, "bitmap width is" +  chatentry.imgthmbnail.getWidth());

            }

            wrapper = (RelativeLayout) row.findViewById(R.id.wrapper);
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.btn_showFSImageActivity = (ImageButton) row.findViewById(R.id.upload_img_thmbnail);
            viewHolder.imgCaptureThbnail = (ImageView) row.findViewById(R.id.upload_img_thmbnail); 
            viewHolder.imgCaptureThbnail.setImageBitmap(chatentry.imgthmbnail);

            row.setTag(viewHolder);

            //testing
            Log.i(TAG,"img received from srv. Here is associtated text content: " + chatentry.content);

            viewHolder.btn_showFSImageActivity.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    Log.i(TAG,"yay button clicked, here is chatentry.content:" + chatentry.content);
                    MainActivity main_act = (MainActivity) v.getContext();
                    main_act.onShowImgInActivity(chatentry.content);
                }
            });

            wrapper.setBackgroundResource(chatentry.isOutbound ? R.drawable.bubble_green : R.drawable.bubble_yellow);
            wrapper.setGravity(chatentry.isOutbound ? Gravity.RIGHT : Gravity.LEFT);

        } else if(chatentry.msgType.equals("TXT")) {
            row = inflater.inflate(R.layout.list_view_row, parent, false);

            wrapper_text_entry = (LinearLayout) row.findViewById(R.id.wrapper_listviewtextrow);
            message = (TextView) row.findViewById(R.id.textview_chatmsg);
            message.setText(chatentry.content);
            message.setBackgroundResource(chatentry.isOutbound ? R.drawable.bubble_green : R.drawable.bubble_yellow);
            wrapper_text_entry.setGravity(chatentry.isOutbound ? Gravity.RIGHT : Gravity.LEFT);

        } else
            Log.e(TAG, "Something has gone terribly wrong in ChatArrayAdapter-getView.");

        return row;
    }

}

AsyncTask 类:

public class ServerTask_UploadContent extends AsyncTask<String, Integer , Void>  {
private String SERVERURL;
private final static String INPUT_IMG_FILENAME = "/imgcapture_temp.jpg";    

//Task state
private final int UPLOADING_PHOTO_STATE  = 0;
private final int SERVER_PROC_STATE  = 1;

private ProgressDialog dialog;
private String fname;                   /* file name for file to be uploaded */
private String uploadFilePath;          /* file name on android storage to be uploaded. */
private MainActivity app;
private static final String TAG = "Kingfisher";

private int lstvwentrnum = -1;              //index were captured image is being saved in listview.

public ServerTask_UploadContent(Context c) {
    super();
    app = (MainActivity) c;
    SERVERURL =  app.getString(R.string.srv_domain) + app.getString(R.string.srv_uploadfile);
}

//upload photo to server
private HttpURLConnection uploadPhoto(FileInputStream fileInputStream)
{
    fname = createTimeStamp() + ".jpg";

    final String lineEnd = "\r\n";
    final String twoHyphens = "--";
    final String boundary = "*****";

    try
    {
        URL url = new URL(SERVERURL);
        // Open a HTTP connection to the URL
        final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        // Allow Inputs
        conn.setDoInput(true);              
        // Allow Outputs
        conn.setDoOutput(true);             
        // Don't use a cached copy.
        conn.setUseCaches(false);

        // Use a post method.
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);

        DataOutputStream dos = new DataOutputStream( conn.getOutputStream() );

        dos.writeBytes(twoHyphens + boundary + lineEnd);

        if( uploadFilePath.compareTo((Environment.getExternalStorageDirectory().toString()  + INPUT_IMG_FILENAME)) == 0 )
            dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + fname +"\"" + lineEnd);
        else
        {
            Log.e(TAG, "Something has gone wrong passing video to  HTTPImgUploader. " +
                    "path passed to class is: " + uploadFilePath + "\n" +
                    "path using getexternalstoragedircotry function is: " + 
                    (Environment.getExternalStorageDirectory().toString()));
            System.exit(-1);
        }

        dos.writeBytes(lineEnd);

        // create a buffer of maximum size
        int bytesAvailable = fileInputStream.available();
        int maxBufferSize = 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);
        byte[] buffer = new byte[bufferSize];

        // read file and write it into form...
        int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        Log.d(TAG, "buffer: "  + buffer);

        while (bytesRead > 0)
        {
            dos.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        }

        // send multipart form data after file data...
        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
        publishProgress(SERVER_PROC_STATE);

        // close streams
        fileInputStream.close();
        dos.flush();

        return conn;
    }
    catch (MalformedURLException ex){
        Log.e(TAG, "error: " + ex.getMessage(), ex);
        return null;
    }
    catch (IOException ioe){
        Log.e(TAG, "error: " + ioe.getMessage(), ioe);
        return null;
    }
}


protected void onPreExecute() 
{ 
    app.addImgToChat(fname);
}

@Override
protected Void doInBackground(String... params) 
{           
    uploadFilePath = params[0];
    processImage(uploadFilePath);

    return null;
}


@Override
protected void onPostExecute(Void param) 
{
    app.sendFileUploadSuccessMsg(fname);
}

【问题讨论】:

    标签: android android-listview android-asynctask


    【解决方案1】:

    您为什么不为您的 ChatListEntry 模型分配一个额外的字段,为每条消息提供唯一的 id?

    如您所说,如果您遇到的问题是此 ArrayList 可以从不同的进程中获取条目,您可以在模型类中保留一个静态值,您可以将其用作参考中任何地方的自动增量值应用。这样你就可以确保每条消息都有一个唯一的标识符。

    我会这样做:

    public class ChatListEntry {
    
        private static int INCREMENTAL_ID = 0;
    
        // Fields:
    
        private int id;
    
        ...
    
        //----
        // Constructor:
        public ChatListEntry(id){
            this.id = id;
        }
    
        public static int getNextId(){
            return INCREMENTAL_ID++;
        }
    
    }
    

    然后按如下方式构造它们:

    ChatListEntry cle = new ChatListEntry(ChatListEntry.getNextId());
    

    然后您可以轻松地将项目 ID 传播回 MainActivity 方法,我假设该方法处理项目布局并使 ListView 无效...

    只需对您的自定义 AsyncTask 进行少量修改,您甚至可以将其桥接回来(如果必须的话)。

    类似:

        public class ServerTask_UploadContent extends AsyncTask<String, Integer , Void> {
    
            private final int messageId = ChatListEntry.getNextId();
    
            protected void onPreExecute() 
            { 
                ChatListEntry newCle = new ChatListEntry(messageId);
                newCle.setFname(fname);
                // You send copy of the object with unique Id:
                app.addImgToChat(newCle);
            }
    
            @Override
            protected Void doInBackground(String... params) 
            {           
                uploadFilePath = params[0];
                processImage(uploadFilePath);
    
                return null;
            }
    
    
            @Override
            protected void onPostExecute(String messageId) 
            {
                // You notify with messageId wich item to point to:
                app.sendFileUploadSuccessMsg(fname,messageId);
            }
        }
    

    然后您可以在sendFileUploadSuccessMsg()ArrayList&lt;ChatListEntry&gt; 中找到该特定项目,将ProgressBar 设置为不可见/替换它,最后调用notifyDataSetChanged() 您的ChatArrayAdapter

    【讨论】:

    • 谢谢。我已经实施了您的解决方案并且它有效。为了从 ArrayList 中查找项目,我在所有元素中搜索正确的 id。有一个更好的方法吗?我有点担心,因为这个搜索是在 MainActivity 中完成的。这会不会导致 UI 性能出现问题?
    • @user3557975 我知道你的意思,但是在自定义元素的 ArrayList 中搜索特定值没有“简单的方法”。如果它是泛型类型的 ArrayList,您可以使用 mArrayList.contains(Object o)。然而,通过 java Collection's 搜索并不是世界上最昂贵的东西 =)。很高兴我能帮上忙。
    • 你认为我应该在另一个线程中进行搜索,以便 UI 线程不会被阻塞吗?我已经测试了代码,它看起来工作正常,但我担心如果手机做很多工作,性能会很差。
    • @user3557975 大多数时候都很好。您不必担心,但如果您坚持,您可以随时尝试使用任务并将结果返回给 UI 线程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    相关资源
    最近更新 更多