I need to dynamically controll the behaviour of a button of a custom listView with AsyncTask

守給你的承諾、 提交于 2019-12-11 06:56:38

问题


Im making an ecommerce app, and on my wall i have many products (which i call items). So all these items are seen through a custom ListView called ListViewAdapter. each row of the customListView has various View elements like TextView, Button, Image, etc. The Button that i have is a follow button (meaning that if the user is following a certain item this button should be seen gray saying followed, if the user is not following it then it should be green saying follow).

So when i load the data in the ListView for the first time i need to do this check to see if an item is beeing followed or not (and this function is done by doing a server call in background and based on the response i know if its beeing followed or not). Plus i need to implement a function that when i click on the follow button of a certain item it does the server call in background (getting a result = 0 if the operation succeded), changes color and text.

I've made an ItemView class that manages the Views of an item, and my question arrises here:

  1. How do i manage the fact that i need to dynamically manage the follow Button (obviously i need to use AsyncTask), but where do i manage it? in the ItemView class or in ListViewAdapter for each row?

  2. I need to manage these 3 server calls all together: get all the items, see which one is followed or not, and if i select an item follow it.

I read a lot that i should use adapter.notifyDataSetChanged() but where and how ?

Im sure im not the first person to ask such a question. I imagine that most ecommerce apps have the same behaviour. Could you please show me how this is done efficiently. Thanks!

Here is my code:

ListViewAdapter class

public class ListViewAdapter extends ArrayAdapter<String> {

    private LayoutInflater inflater = null;

    public Context context; 
    public int layoutResourceId;
    public ArrayList<Item> items;
    public Bitmap icon;

    public ListViewAdapter(Context context, int listviewItemRow, ArrayList<Item> items, Bitmap icon) {
        // TODO Auto-generated constructor stub
        super(context, listviewItemRow);
        this.items = items;
        this.context = context;
        this.icon = icon;
    }

    @Override
    public void remove(String object) {
        // TODO Auto-generated method stub
        super.remove(object);
    }

    @Override
    public int getCount() {
        return items.size();
    }

    public Item getItem(Item position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {

        ItemView view;
        if (convertView == null) {
            inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = (ItemView) inflater.inflate(R.layout.listview_item_row, null);
        } else {
            view = (ItemView) convertView;
        }

        Item item = items.get(position);
        view.showItems(item);
        view.setOnClickListener(new OnItemClickListener(position));

        return view;
    }

    private class OnItemClickListener implements OnClickListener {

        private int mPosition;

        private OnItemClickListener(int position){
            mPosition = position;
        }

        @Override
        public void onClick(View v) {

            Log.i("onListItemClickList", "Item clicked: " + mPosition);
            Toast.makeText(context, "Message " + Integer.toString(mPosition), Toast.LENGTH_SHORT).show();

            Intent intent = new Intent(context, DettagliActivity.class);
            Bundle bundle = new Bundle();
            bundle.putInt("id", mPosition);
            intent.putExtras(bundle);
            context.startActivity(intent);
        }   
    }

ItemView class

public class ItemView extends LinearLayout implements AsyncResponse{

    public TextView prezzo;
    public TextView scadenza;
    public TextView followers;
    public ImageView ic_thumbnail;
    public ProgressBar hProgressBar;
    public ToggleButton followButton;
    public String nextFollowAction = "";
    public Integer result1 = 77;
    public int statusCode;
    public Item item;

    public BackgroundTask mBackgroundTask = null;

    public ItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //mBackgroundTask.delegate = this;
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        prezzo = (TextView)findViewById(R.id.tvPrezzo);
        scadenza = (TextView)findViewById(R.id.tvScadenza);
        followers = (TextView)findViewById(R.id.tvFollowers);
        ic_thumbnail = (ImageView)findViewById(R.id.ic_thumbnail);
        hProgressBar = (ProgressBar)findViewById(R.id.hProgressBar);
        followButton = (ToggleButton)findViewById(R.id.btnFollow);
    }

    public void showItems(final Item item) {
        prezzo.setText(item.getPrezzo());
        ic_thumbnail.setImageBitmap(item.getIcon());
        scadenza.setText(item.getScadenza());
        followers.setText("Followers:    " + item.getFollowers());
        hProgressBar.setProgress(item.getCoefficient());


        askForFollowing("kCheckFollowAction", item, 3);


        mBackgroundTask = new BackgroundTask(this);
    mBackgroundTask.execute(item.getId(), (long)3);

    followButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    // The toggle is enabled
                    Log.i("followButton", "toggle enabled");
                    followButton.getTextOn();
                    mBackgroundTask = new BackgroundTask(ItemView.this);
                    mBackgroundTask.execute(item.getId(), (long)1);
                    //askForFollowing(result1, nextFollowAction, item);
                    //askForFollowing(statusCode, nextFollowAction, item);
                    increaseFollowers(item);
                    followButton.setBackgroundResource(R.drawable.action_object_button_gray);
                } else {
                    // The toggle is disabled
                    Log.i("followButton", "toggle disabled");
                    mBackgroundTask = new BackgroundTask(ItemView.this);
                    mBackgroundTask.execute(item.getId(), (long)2);
                    //askForFollowing(result1, nextFollowAction, item);
                    followButton.getTextOff();
                    followButton.setBackgroundResource(R.drawable.action_object_button_green);
                    decreaseFollowers(item);
                }
            }
        });
    }

    public void setStatusCode(int statusCode){
        this.statusCode = statusCode;
    }


    public int getStatusCode(Item item, int follow){
        //add thread that waits untill you have the statusCode
        mBackgroundTask = new BackgroundTask(ItemView.this);
        mBackgroundTask.execute(item.getId(), (long) follow);

        return statusCode;
    }

    public void askForFollowing(String nextFollowAction, Item item, int follow){
         Log.i("The statusCode is", Integer.toString(statusCode));
         Log.i("The nextFollowAction is", nextFollowAction);

         int statusCode = getStatusCode(item, follow);

         //Status code: 0 --> OK
         if(statusCode == 0) {
            Log.i("changeFollowStatus(nextFollowAction);", "changeFollowStatus(nextFollowAction);");
            nextFollowAction = "kCheckFollowAction";
            changeFollowStatus(nextFollowAction, item);
         }

         // Status code 108 --> Oggetto già seguito
         else if ((statusCode == 108) && (nextFollowAction.contains("kCheckFollowAction"))) {
            Log.i("statusCode == 108", "statusCode == 108");
            nextFollowAction = "kUnfollowAction";
            followButton.setEnabled(true);
            followButton.setBackgroundResource(R.drawable.action_object_button_gray);
            followButton.setText("seguito");
         }

         // Status code 122 --> Oggetto non ancora seguito
         else if ((statusCode == 122) && (nextFollowAction.contains("kCheckFollowAction"))) {
            Log.i("statusCode == 122", "statusCode == 122");
            nextFollowAction = "kFollowAction";
            followButton.setEnabled(false);
            followButton.setBackgroundResource(R.drawable.action_object_button_green);
            followButton.setText("segui");
         }  
    }

    public void changeFollowStatus(String action, Item item){
        Log.i("changeFollowStatus action", action);

        if(action.contains("kFollowAction")) {
            Log.i("changeFollowStatus", "1");
            nextFollowAction = "kUnfollowAction";
            followButton.setBackgroundResource(R.drawable.action_object_button_gray);
            followButton.setText("seguito");
            followButton.getTextOn();
            increaseFollowers(item);        
        }
        else if(action.contains("kUnfollowAction")){
            Log.i("changeFollowStatus", "2");
            nextFollowAction = "kFollowAction";
            followButton.setBackgroundResource(R.drawable.action_object_button_green);
            followButton.setText("segui");
            followButton.getTextOff();
            decreaseFollowers(item);
        }
    }

    public void increaseFollowers(Item item){
        int updatedFollowers = Integer.parseInt(item.getFollowers()) + 1;
        item.setFollowers(Integer.toString(updatedFollowers));
        followers.setText("Followers:    " + item.getFollowers());
    }

    public void decreaseFollowers(Item item){
        int updatedFollowers = Integer.parseInt(item.getFollowers()) - 1;
        item.setFollowers(Integer.toString(updatedFollowers));
        followers.setText("Followers:    " + item.getFollowers());
    }

    @Override
    public Integer processFinish(Integer result) {
        return result;
    }

    /**
     * Represents an asynchronous  task used to download
     * information from the webserver and display the results
     */
    public class BackgroundTask extends AsyncTask<Long, Void, Integer> {

        //public AsyncResponse delegate;
        private AsyncResponse listener;

        public BackgroundTask(AsyncResponse listener){
            this.listener = listener;
        }

        @Override
        protected Integer doInBackground(Long... params) {
            // TODO: attempt authentication against a network service.

            int i = MVPFunctions.getInstance().followItem(SessionManager.getUserDetails().get("login"), SessionManager.getUserDetails().get("password"), params[0], params[1].intValue());
            return i;
        }

        @Override
        protected void onPreExecute(){
            /*
             * This is executed on UI thread before doInBackground(). It is
             * the perfect place to show the progress dialog.
             */
        }

        @Override
        protected void onPostExecute(Integer result) {
            mBackgroundTask = null;
            result1 = listener.processFinish(result);

            setStatusCode(result);

            //delegate.processFinish(result);
            //ItemView
            //Log.i("onPostExecute statusCode", Integer.toString(success) + " = " + Integer.toString(statusCode));
        }

        @Override
        protected void onCancelled() {
            mBackgroundTask = null;
            //showProgress(false);
        }
    }
}

CompraFragment class

public class CompraFragment extends ListFragment {

    public ListView listView;
    public ListViewAdapter adapter;
    public boolean loading = false;
    public boolean get_all_items = false;
    //public PullToRefreshScrollView mPullRefreshScrollView;

    /**
     * Keep track of the login task to ensure we can cancel it if requested.
     */
    private DownloadTask mDownloadTask = null;
    private Boolean firstTime = true;

    //public ArrayList<HashMap<String, Object>> items = new ArrayList<HashMap<String, Object>>();
    public ArrayList<Item> items = new ArrayList<Item>();
    public static ArrayList<Long> ids = new ArrayList<Long>();
    public Bitmap icon;
    public int currentItemId = 0;
    public Boolean noItems = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        //View rootView = inflater.inflate(R.layout.fragment_compra, false); 
        View rootView = inflater.inflate(R.layout.fragment_compra, container, false); 

        // now you must initialize your list view
        listView = (ListView) rootView.findViewById(android.R.id.list); 

        //mDownloadTask.execute((Void) null);   
        mDownloadTask = new DownloadTask();
        mDownloadTask.execute(currentItemId);

        return rootView;
    }

    /**
     * Represents an asynchronous  task used to download
     * information from the webserver and display the results
     */
    public class DownloadTask extends AsyncTask<Integer, Void, Boolean> {

        private ProgressDialog progressDialog;

        @Override
        protected Boolean doInBackground(Integer... params) {
            // TODO: attempt authentication against a network service.

            if (firstTime){
                ids = MVPFunctions.getInstance().search();
                firstTime = false;
            }

            if (ids.isEmpty()){
                noItems = true;
                return false;
            }

            int current_id = params[0];


            // 5 elements at a time
            int counter = 0;
            int size = ids.size();  

            while (counter <= 5 && (current_id < size)) {
                items.add(MVPFunctions.getInstance().getItem(ids.get(current_id)));
                current_id++;
                counter++;
                currentItemId = current_id;
            }

            if(current_id == size){
                get_all_items = true;
            }

            Log.i("current_id    2", Integer.toString(current_id));
            return true;
        }

        @Override
        protected void onPreExecute(){
            /*
             * This is executed on UI thread before doInBackground(). It is
             * the perfect place to show the progress dialog.
             */
            progressDialog = ProgressDialog.show(getActivity(), "", "Downloading Content...");
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            //mDownloadTask = null;

            // dismiss the dialog after getting all products
            progressDialog.dismiss();
            loading = false;
            //showProgress(false);

            Log.i("onPostExecute", "onPostExecute");

            if (noItems){
                Log.i("doInBackground2", "items null");
                Toast.makeText(getActivity(), "Non ci sono elementi da caricare", Toast.LENGTH_LONG).show();
            } else {
                        // updating UI from Background Thread
                ListViewAdapter adapter = new ListViewAdapter(getActivity(),R.layout.listview_item_row, items, icon);
                        // updating listview
                    listView.setAdapter(adapter);

                    listView.setOnScrollListener(new EndlessScrollListener());                          
            }

        }

        @Override
        protected void onCancelled() {
            mDownloadTask = null;
            //showProgress(false);
        }

    }

    public class EndlessScrollListener implements OnScrollListener {

        private int visibleThreshold = 6;
        private int previousTotal = 0;


        public EndlessScrollListener() {
        }
        public EndlessScrollListener(int visibleThreshold) {
            this.visibleThreshold = visibleThreshold;
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            if (loading) {
                if (totalItemCount > previousTotal) {
                    loading = false;
                    previousTotal = totalItemCount;
                }
            }

            if (!loading && visibleItemCount != 0 && ((firstVisibleItem + visibleItemCount) >= (totalItemCount))) {
                if ((currentItemId <= ids.size()) && !get_all_items){               
                    loading = true;
                    mDownloadTask = new DownloadTask();
                    mDownloadTask.execute(currentItemId);
                }
            }
            /*
            if (visibleItemCount != 0 && ((firstVisibleItem + visibleItemCount) >= (totalItemCount))) {
                if (currentItemId <= ids.size()){
                    Log.i("3333 if", "3333 if");
                    mDownloadTask = new DownloadTask();
                    mDownloadTask.execute(currentItemId);
                }
            }
            */
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
        }
    }
}

回答1:


I've done something similar! I made added togglebuttons dynamicly in my adapter class

Check out this link for the example:

Check ToggleButton dynamicly




回答2:


Having as a base that even more important than showing an updated follow/followed status of your Button is the responsiveness of your app, I'd do the following:

  • You shouldn't delay the loading of that ListView just to load it with the updated data. Here, responsiveness is a 90% responsibility of the network speed (or even availibility of the network) - I'd load all of the items without the updated state (say, showing the 'Follow' button).

  • You know you'll need to update the state of your buttons. Immediately after initializing your adapter, I'd start running your AsyncTask.

  • As a "communication method", I'd recommend a Local Broadcast. The reason is simple: You process your information within the AsyncTask, and you need to assign it through your adapter. The communication between them using references is not a trivial thing. Plus, you need as much speed as possible. Plus, the data you'll send (assuming you'll need the row Id or a tag, and a Boolean to mark whether that row must show the Follow or Followed state) is serializable, it makes it ideal for that case.

So, in the practical side that's what I'd do:

In your array adapter, concretely in your getView() method, when you inflate your rows, mark each button of the row with an unique identifier (say, the item name). You can do this via setTag("itemname") on the View object (the second parameter).

Still in your array adapter extension, define a local broadcast receiver like this:

class MyItemBroadcast extends BroadcastReceiver {
  @Override
  public void onReceive(final Context context, final Intent intent) {
    if (intent.getAction().equals("ItemUpdate")) {
      final String itemname = intent.getStringExtra("itemname");
      final Boolean follow = intent.getBooleanExtra("is_following");
      if (itemname != null) {
        final ListView myListView = (ListView) findViewById(R.id.your_listview_id);
        final Button = (Button) myListView.findViewWithTag(itemname);

        if (follow) {
          // Here you would assign one of the layouts (follow or already followed)
          ...
        }
        else {
          // The other one
          ...
        }

        // You have to call this to make the observers update the ListView layout.
        // One of them is your activity/fragment itself, who has the instance of your
        // adapter, so you're telling it to update the layout with this change
        notifyDataSetChanged();
      }
    }
  }
}

// Now you need to declare the receiver and register it
final MyItemBroadcast bc_receive = new MyItemBroadcast();
LocalBroadcastManager.getActivity().registerReceiver(bc_receive, new IntentFilter("ItemUpdate"));

Having done that, then you just need to process the events in your AsyncTask and send the "signal" to the receiver you've declared, so within your AsyncTask, whenever you need to update an item, just issue something like that:

final Intent intentResult = new Intent("ItemUpdate");
intentResult.putExtra("itemname", "my_item_to_update");
intentResult.putExtra("is_following", true);
LocalBroadcastManager.getInstance(this).sendBroadcast(intentResult);          

With that, your extended ArrayListAdapter should be updated each time a new event is processed. I haven't tried anything of this, you may find typos or little mistakes, and I'm more accostumed to work with Activities than with Fragments, so maybe you'll have to find some analogies (you will across StackOverflow for sure), but this is the idea and it works. Don't forget to unregisterReceiver() once you don't need it anymore!




回答3:


  • To dispatch event to children of a viewGroup, use onInterceptTouchEvent: link

  • To manage the refresh of the 'Follow' button:

    1. in the getView method of your ListViewAdapter, check if the current item is followed and change the button background. Set a tag to your buttonView which could be the index of the item in the ArrayList items (use setTag on buttonView) and set an onClickListener (the tag on a view allows you to attach any type of data to a view).

    2. In your onClickListener, get the index of the item by calling view.getTag() and retrieve the corresponding item. Then execute an asyncTask in order to run your http request with item id. I guess that this request store on your webserver that this item is followed.

    3. define in your adapter a method to set an item as followed with the position as argument: void setIsFollowed(int position). In onPostExecute of your AsyncTask, run the method setIsFollowed(position) in order to change the adapter source. Then, force the listView to update it with the modified array by calling listView.notifyDataSetChanged

I hope that's clear...

EDIT:

public void setIsFollowed(int position, boolean isfollowed) {  
 Item myItem = Items.get(position);   

 if (myItem != null)   
    //don't forget to add "boolean isFollowed" in the Item class   
    myItem.isFollowed = isfollowed;
 }



回答4:


Try to get all "following data" before you populate the ListView.

If you can edit the server side, write a method for it like get "follow" status of these ids . then your adapters getView method can check the boolean (something like : isFollowing()) data and change the button according to it. By this way, all you have to do is changing the data set and notify the adapter after it.

it is ok to change singular data with asynctask in list view.

EDIT :

if it is so, you have to call your asynctask in your adapters getView() method

getView(){
    //convertView ect. blah. do the same stuff to populate your list
    new CheckfollowTask(btnFollow, items.get(position)).execute();


}

//Type parameters can change in your need this is just an example, a pseudo.
class CheckfollowTask extends AsyncTask<Void,Void,Void>{

Button btnFollow;
Item item;
       CheckfollowTask(Button btnFollow,Item item){
       this.btnFollow=btnFollow;
       this.item=item;
       }
      donInBackGround(){
      //get data from server
      }
      onPostExecute(){
      //set btnFollow's background to something due to your response from server.
      //don't forget to set your list items followin status.
      item.setFollow(response);
      }

}


来源:https://stackoverflow.com/questions/21158397/i-need-to-dynamically-controll-the-behaviour-of-a-button-of-a-custom-listview-wi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!