问题
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 View
s of an item, and my question arrises here:
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?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
: linkTo manage the refresh of the 'Follow' button:
in the
getView
method of yourListViewAdapter
, 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).In your
onClickListener
, get the index of the item by callingview.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.define in your adapter a method to set an item as followed with the position as argument:
void setIsFollowed(int position)
. InonPostExecute
of your AsyncTask, run the methodsetIsFollowed(position)
in order to change the adapter source. Then, force the listView to update it with the modified array by callinglistView.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