I writing time-tracking app, every row of listview has two buttons(Start and Stop) and some textView to display elapsed time.
After scrolling in the listview, elements are swapped.
I'm not sure in my solution, but i put in the model class, ViewHolder object for getting acces for changing view elements.
Here is the fragment of code model
private String name;
private Boolean isStart=false;
private Long elapsedTime=0L,seconds=0L,hours=0L,minutes=0L,lastPause=0L,updateTime=0L,startTime=0L,days=0L;
private Runnable updateTimeThread=new Runnable() {
#Override
public void run() {
if(isStart && startTime!=0) {
updateTime = ((System.currentTimeMillis() - startTime) + lastPause);
seconds = updateTime / 1000;
minutes = seconds / 60;
hours = minutes / 60;
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 24;
holder.days.setText(String.format("%04d", days));
holder.hours.setText(String.format("%02d", hours));
holder.minutes.setText(String.format("%02d", minutes));
holder.seconds.setText(String.format("%02d", seconds));
Log.d("myTag",name+" "+seconds);
MainActivity.handler.post(this);
}
}
};
MyAdapter.ViewHolder holder;
public MyAdapter.ViewHolder getHolder() {
return holder;
}
public void setHolder(MyAdapter.ViewHolder holder) {
this.holder = holder;
}
public Runnable getRunnable() {
return updateTimeThread;
}
Adapter's fragment code
public View getView(final int position, final View convertView, ViewGroup parent) {
View row = convertView;
final Tracker tracker = trackerList.get(position);
final Runnable updateTimeThread=tracker.getRunnable();
View.OnClickListener onClickListener;
ViewHolder holder;
if(row == null){
holder = new ViewHolder();
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(R.layout.row,parent,false);
holder.name = (TextView)row.findViewById(R.id.tvName);
holder.days = (TextView)row.findViewById(R.id.tvDays);
holder.hours = (TextView)row.findViewById(R.id.tvHours);
holder.minutes = (TextView)row.findViewById(R.id.tvMinutes);
holder.seconds = (TextView)row.findViewById(R.id.tvSeconds);
holder.start = (Button)row.findViewById(R.id.btStart);
holder.stop = (Button)row.findViewById(R.id.btStop);
row.setTag(holder);
}else {
holder = (ViewHolder) row.getTag();
}
holder.start.setEnabled(true);
holder.stop.setEnabled(false);
holder.name.setText(tracker.getName());
final ViewHolder finalHolder = holder;
if(tracker.getIsStart()){
holder.start.setEnabled(false);
holder.stop.setEnabled(true);
}
onClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btStart:
tracker.setStartTime(System.currentTimeMillis());
tracker.setIsStart(true);
tracker.setHolder(finalHolder);
MainActivity.handler.post(updateTimeThread);
finalHolder.start.setEnabled(false);
finalHolder.stop.setEnabled(true);
break;
case R.id.btStop:
tracker.setLastPause(tracker.getUpdateTime());
MainActivity.handler.removeCallbacks(updateTimeThread);
finalHolder.stop.setEnabled(false);
finalHolder.start.setEnabled(true);
tracker.setIsStart(false);
break;
}
}
};
holder.start.setOnClickListener(onClickListener);
holder.stop.setOnClickListener(onClickListener);
return row;
}
static class ViewHolder{
TextView name,days,hours,minutes,seconds;
Button start,stop;
}
I just add these two methods to my adapter)
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
UPDATE
But with Loader Callback problem is still the same. I press Start on last item and scroll down, values not visible, scroll up a bit and values visible. Pity.
UPDATE 2 This article solve my problem, i just change layout-height and laoyut-weight from "wrap_content" to "fill_parent" to avoid multiple calling getView() method.
http://gmariotti.blogspot.com/2013/06/tips-for-listview-view-recycling-use.html
Change this part
holder.start.setEnabled(true);
holder.stop.setEnabled(false);
holder.name.setText(tracker.getName());
final ViewHolder finalHolder = holder;
if(tracker.getIsStart()){
holder.start.setEnabled(false);
holder.stop.setEnabled(true);
}
to this
final ViewHolder finalholfder = holder;
finalholfder.start.setEnabled(true);
finalholfder.stop.setEnabled(false);
finalholfder.name.setText(tracker.getName());
if(tracker.getIsStart()){
finalholfder.start.setEnabled(false);
finalholfder.stop.setEnabled(true);
} else{
finalholfder.start.setEnabled(true);
finalholfder.stop.setEnabled(false);
}
And this
holder.start.setOnClickListener(onClickListener);
holder.stop.setOnClickListener(onClickListener);
to this
finalholfder.start.setOnClickListener(onClickListener);
finalholfder.stop.setOnClickListener(onClickListener);
Related
Im coding a chat for android and right now im adding messages when the user scrolls up, kind of like discord and most other chats out there. Every message loads up fine. So in my code you can click on the message and it will display the time it was posted. So if it was a few secs ago it would show 30secs, or 40secs. So its pretty much timeAgo. The problem comes that if you click on a message that should be at the top, the timeAgo info is also displayed in the one at the Bottom??? which makes no sense to me because im not clicking the one at the bottom and my code is pretty straight forward. Here is my code
public class RecyclerAdapterChatRequestMyMessage extends RecyclerView.Adapter{
List<String> message;
List<Long> time;
List<String> profilePic;
List<String> name;
List<Integer> type;
Context context;
public RecyclerAdapterChatRequestMyMessage(Context context, List<String> message, List<Long> time,List<String> profilePic,List<String> name,List<Integer> type) {
this.message=message;
this.time=time;
this.name=name;
this.context = context;
this.profilePic=profilePic;
this.type=type;
}
#Override
public int getItemViewType(int position) {
if(type.get(position)==0){
return 0;
}
return 1;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if(viewType == 0) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.from(parent.getContext()).inflate(R.layout.recycler_chat_request_my_message, parent, false);
RecyclerAdapterChatRequestMyMessage.ViewHolder1 viewHolder1 = new RecyclerAdapterChatRequestMyMessage.ViewHolder1(view);
return viewHolder1;
}else {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.from(parent.getContext()).inflate(R.layout.recycler_chat_request_their_message, parent, false);
RecyclerAdapterChatRequestMyMessage.ViewHolder2 viewHolder2 = new RecyclerAdapterChatRequestMyMessage.ViewHolder2(view);
return viewHolder2;
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
if(type.get(position)==0) {
ViewHolder1 vh = (ViewHolder1) holder;
vh.ivProfilePic.setImageDrawable(null);
Glide.with(vh.itemView.getContext()).load(profilePic.get(position)).into(vh.ivProfilePic);
vh.textViewName.setText(name.get(position));
vh.textViewMessage.setText(message.get(position));
vh.textViewTime.setText(time.get(position).toString());
}else {
ViewHolder2 vh2 = (ViewHolder2)holder;
vh2.ivProfilePic2.setImageDrawable(null);
Glide.with(vh2.itemView.getContext()).load(profilePic.get(position)).into(vh2.ivProfilePic2);
vh2.textViewName2.setText(name.get(position));
vh2.textViewMessage2.setText(message.get(position));
vh2.textViewTime2.setText(time.get(position).toString());
}
}
#Override
public int getItemCount() {
return message.size();
}
public class ViewHolder1 extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewMessage;
TextView textViewTime;
TextView textViewTimeAgo;
ImageView ivProfilePic;
LinearLayout ll;
boolean x;
public ViewHolder1(#NonNull View itemView) {
super(itemView);
textViewTime= itemView.findViewById(R.id.myMessageTimeTextView8);
textViewName = itemView.findViewById(R.id.myMessageNameTextView7);
ivProfilePic = itemView.findViewById(R.id.myMessageImageView4);
textViewMessage= itemView.findViewById(R.id.myMessageTextView6);
textViewTimeAgo= itemView.findViewById(R.id.myMessageTimeAgo);
ll= (LinearLayout)itemView.findViewById(R.id.linearLayoutMyMessageTime);
x=false;
ll.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(x==false) {
x=true;
long timeDifference = (Instant.now().toEpochMilli() - Long.parseLong(textViewTime.getText().toString())) / 1000;
long timeUnit;
if(timeDifference < 60){
timeUnit = DateUtils.SECOND_IN_MILLIS;
} else if(timeDifference < 3600){
timeUnit = DateUtils.MINUTE_IN_MILLIS;
} else if (timeDifference < 86400){
timeUnit = DateUtils.HOUR_IN_MILLIS;
}else if (timeDifference < 31536000){
timeUnit = DateUtils.DAY_IN_MILLIS;
} else {
timeUnit = DateUtils.YEAR_IN_MILLIS;
}
CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(Long.parseLong(textViewTime.getText().toString()), Instant.now().toEpochMilli(),
timeUnit, DateUtils.FORMAT_ABBREV_RELATIVE);
System.out.println(timeAgo.toString());
textViewTimeAgo.setText(timeAgo.toString());
textViewMessage.setVisibility(view.GONE);
textViewTimeAgo.setVisibility(view.VISIBLE);
}else{
System.out.println("Clicked again");
x=false;
textViewMessage.setVisibility(view.VISIBLE);
textViewTimeAgo.setVisibility(view.GONE);
}
}
});
}
}
public class ViewHolder2 extends RecyclerView.ViewHolder {
TextView textViewName2;
TextView textViewMessage2;
TextView textViewTime2;
ImageView ivProfilePic2;
TextView textViewTimeAgo2;
LinearLayout ll2;
boolean x2;
public ViewHolder2(#NonNull View itemView) {
super(itemView);
textViewTime2= itemView.findViewById(R.id.theirMessageTimeTextView8);
textViewName2 = itemView.findViewById(R.id.theirNameTextView7);
ivProfilePic2 = itemView.findViewById(R.id.theirMessageImageView4);
textViewMessage2= itemView.findViewById(R.id.theirMessageTextView6);
textViewTimeAgo2= itemView.findViewById(R.id.theirMessageTimeAgo);
ll2= (LinearLayout)itemView.findViewById(R.id.linearLayoutTheirMessageTime);
x2=false;
ll2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(x2==false) {
x2=true;
long timeDifference = (Instant.now().toEpochMilli() - Long.parseLong(textViewTime2.getText().toString())) / 1000;
long timeUnit;
if(timeDifference < 60){
timeUnit = DateUtils.SECOND_IN_MILLIS;
} else if(timeDifference < 3600){
timeUnit = DateUtils.MINUTE_IN_MILLIS;
} else if (timeDifference < 86400){
timeUnit = DateUtils.HOUR_IN_MILLIS;
}else if (timeDifference < 31536000){
timeUnit = DateUtils.DAY_IN_MILLIS;
} else {
timeUnit = DateUtils.YEAR_IN_MILLIS;
}
CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(Long.parseLong(textViewTime2.getText().toString()), Instant.now().toEpochMilli(),
timeUnit, DateUtils.FORMAT_ABBREV_RELATIVE);
System.out.println(timeAgo.toString());
textViewTimeAgo2.setText(timeAgo.toString());
textViewMessage2.setVisibility(view.GONE);
textViewTimeAgo2.setVisibility(view.VISIBLE);
}else{
System.out.println("Clicked again");
x2=false;
textViewMessage2.setVisibility(view.VISIBLE);
textViewTimeAgo2.setVisibility(view.GONE);
}
}
});
}
}
}
I am set MaterialTextView inside RelativeLayout and set RelativeLayout size programmatically different size for every device.
Then i have using ViewTreeObserver to set setMaxLines and setEllipsize in MaterialTextView but i am facing some problem show the text in MaterialTextView using RecyclerView.Adapter.
I am using load more RecyclerView i am getting all data then after show text automatically in list and also notify data adapter then show text.
not showing text inside MaterialTextView in list
phone lock on/off then showing data in MaterialTextView
set recyclerview in fragment
recyclerView = view.findViewById(R.id.recyclerView_sub_category);
recyclerView.setHasFixedSize(true);
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter code
public class SubCategoryAdapter extends RecyclerView.Adapter {
private final int VIEW_TYPE_LOADING = 0;
private final int VIEW_TYPE_ITEM = 1;
private final int VIEW_TYPE_QUOTES = 2;
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
other data load
} else if (viewType == VIEW_TYPE_QUOTES) {
View v = LayoutInflater.from(activity).inflate(R.layout.quotes_adapter, parent, false);
return new Quotes(v);
} else if (viewType == VIEW_TYPE_LOADING) {
progressbar load
}
return null;
}
#Override
public void onBindViewHolder(#NonNull final RecyclerView.ViewHolder holder, #SuppressLint("RecyclerView") final int position) {
if (holder.getItemViewType() == VIEW_TYPE_ITEM) {
final ViewHolder viewHolder = (ViewHolder) holder;
// ------------- code -----
} else if (holder.getItemViewType() == VIEW_TYPE_QUOTES) {
final Quotes quotes = (Quotes) holder;
quotes.relativeLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, columnWidth / 2));
Typeface typeface = Typeface.createFromAsset(activity.getAssets(), "text_font/" + subCategoryLists.get(position).getQuote_font());
quotes.textView.setTypeface(typeface);
quotes.textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
quotes.textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int noOfLinesVisible = quotes.textView.getHeight() / quotes.textView.getLineHeight();
quotes.textView.setMaxLines(noOfLinesVisible);
quotes.textView.setEllipsize(TextUtils.TruncateAt.END);
quotes.textView.setText(subCategoryLists.get(position).getStatus_title());
}
});
}
}
#Override
public int getItemCount() {
return subCategoryLists.size() + 1;
}
#Override
public int getItemViewType(int position) {
if (position != subCategoryLists.size()) {
if (subCategoryLists.get(position).getStatus_type().equals("quote")) {
return VIEW_TYPE_QUOTES;
} else {
return VIEW_TYPE_ITEM;
}
} else {
return VIEW_TYPE_LOADING;
}
}
public class Quotes extends RecyclerView.ViewHolder {
private RelativeLayout relativeLayout;
private MaterialTextView textView;
public Quotes(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView_quotes_adapter);
relativeLayout = itemView.findViewById(R.id.rel_quotes_adapter);
}
}
}
I am not completely sure about the problem that you are having there. However, I think I found some problems in your code.
The first problem that I see in setting up your RecyclerView is setting the layout size fixed by the following.
recyclerView.setHasFixedSize(true);
Looks like you are changing the item layout size dynamically. Hence you need to remove the line above while setting up your RecyclerView.
The second problem that I see is, there is no textView_category in the ViewHolder for Quote. Hence the following should throw an error.
quotes.textView_category.setText(subCategoryLists.get(position).getCategory_name());
One problem I can see is that you are calling
quotes.textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
in the first line of your listener callback no matter what. In a RecyclerView the first few times this is called the layout might still be unmeasured but you're cancelling the listener callback after the first callback. So this line fires, and if the view is still unmeasured then the next few lines are going to fail and your code will get no more opportunities to fill the view. Try checking the measuredWidth or measuredHeight of your view for something greater than 0 before cancelling future listener callbacks.
I have using this. works perfectly
textView.setText(subCategoryLists.get(position).getStatus_title());
textView.post(new Runnable() {
#Override
public void run() {
ViewGroup.LayoutParams params = textView.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
final int widthSpec = View.MeasureSpec.makeMeasureSpec(textView.getWidth(), View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(textView.getHeight(), View.MeasureSpec.UNSPECIFIED);
textView.measure(widthSpec, heightSpec);
textView.setMaxLines(heightSpec / textView.getLineHeight());
textView.setEllipsize(TextUtils.TruncateAt.END);
}
});
I have implemented the code to show ads after every 3 item but first item it show after 4 items rest working good
Below is the code I have tried to achieve this.
I want to show ad after every 3 items.
I have taken two constant variable to manage
private class AdsAdapter extends RecyclerView.Adapter<AdsAdapter.Holder> {
List<Ads> list;
List<String> stringList;
private static final int AD_TYPE = 2;
private static final int CONTENT_TYPE = 1;
public AdsAdapter(List<Ads> list, List<String> stringList) {
this.list = list;
this.stringList = stringList;
}
#Override
public int getItemViewType(int position) {
if (position>1 && position % 4 == 0) {
return AD_TYPE;
}
return CONTENT_TYPE;
}
#NonNull
#Override
public Holder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
Log.e("TYPE", viewType + "");
View listItem;
Holder viewHolder = null;
switch (viewType) {
case AD_TYPE:
listItem =
layoutInflater.inflate(R.layout.list_item1, parent,
false);
viewHolder = new Holder(listItem);
break;
case CONTENT_TYPE:
listItem =
layoutInflater.inflate(R.layout.list_item, parent,
false);
viewHolder = new Holder(listItem);
break;
}
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull Holder holder, int
position) {
int viewType=getItemViewType(position);
switch (viewType)
{
case AD_TYPE:
holder.imageView.setVisibility(View.VISIBLE);
break;
case CONTENT_TYPE:
holder.textView.setVisibility(View.VISIBLE);
holder.textView.setText(stringList.get(position));
break;
}
}
#Override
public int getItemCount() {
return stringList.size();
}
public class Holder extends RecyclerView.ViewHolder {
TextView textView;
ImageView imageView;
public Holder(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_string);
imageView = itemView.findViewById(R.id.iv);
}
}
}
In adapter position starts from 0 so you have to do position+1
#Override
public int getItemViewType(int position) {
if (position>1 && (position+1) % 4 == 0) {
return AD_TYPE;
}
return CONTENT_TYPE;
}
I am creating an app that requires a ListView with an undetermined number of elements, each of which has a timer that counts down from a variable number. I am able to successfully make one of them count down, but I can't figure out how to include a timer in each element of the ListView.
I am currently using a CountDownTimer (make sure to capitalize the D if copying from the website, they have it wrong).
Any code or sources to point me in the right direction are much appreciated.
Here is my current EventAdapter class, it sets the text displayed in each ListView element's TextView. What I need to do is make the TextView count down every second. Since each element of the ListView is displaying something different, I suppose I need a way of differentiating each element.
I could just update the whole list every second, but there are other elements I have not included such as images loaded from the internet that it would be impractical to refresh every second.
private class EventAdapter extends ArrayAdapter<Event>
{
private ArrayList<Event> items;
public EventAdapter(Context context, int textViewResourceId, ArrayList<Event> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
}
Event e = items.get(position);
if (e != null) {
TextView tv = (TextView) v.findViewById(R.id.text);
if (tv != null)
tv.setText(e.getName());
}
return v;
}
}
This is an example of the way I do it and it works perfect:
public class TestCounterActivity extends ListActivity
{
TestAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Example values
ArrayList<Date> values = new ArrayList<Date>();
values.add(new Date(1482464366239L));
values.add(new Date(1480464366239L));
values.add(new Date(1470464366239L));
values.add(new Date(1460464366239L));
values.add(new Date(1450464366239L));
values.add(new Date(1440464366239L));
values.add(new Date(1430464366239L));
values.add(new Date(1420464366239L));
values.add(new Date(1410464366239L));
values.add(new Date(1490464366239L));
adapter = new TestAdapter(this, values);
setListAdapter(adapter);
}
#Override
protected void onStop()
{
super.onStop();
// Dont forget to cancel the running timers
adapter.cancelAllTimers();
}
}
And this is the adapter
public class TestAdapter extends ArrayAdapter<Date>
{
private final Activity context;
private final List<Date> values;
private HashMap<TextView,CountDownTimer> counters;
static class TestViewHolder
{
public TextView tvCounter;
}
public TestAdapter(Activity context, List<Date> values)
{
super(context, R.layout.test_row, values);
this.context = context;
this.values = values;
this.counters = new HashMap<TextView, CountDownTimer>();
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View rowView = convertView;
if(rowView == null)
{
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(R.layout.test_row, null);
final TestViewHolder viewHolder = new TestViewHolder();
viewHolder.tvCounter = (TextView) rowView.findViewById(R.id.tvCounter);
rowView.setTag(viewHolder);
}
TestViewHolder holder = (TestViewHolder) rowView.getTag();
final TextView tv = holder.tvCounter;
CountDownTimer cdt = counters.get(holder.tvCounter);
if(cdt!=null)
{
cdt.cancel();
cdt=null;
}
Date date = values.get(position);
long currentDate = Calendar.getInstance().getTime().getTime();
long limitDate = date.getTime();
long difference = limitDate - currentDate;
cdt = new CountDownTimer(difference, 1000)
{
#Override
public void onTick(long millisUntilFinished)
{
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
String sDate = "";
if(millisUntilFinished > DateUtils.DAY_IN_MILLIS)
{
days = (int) (millisUntilFinished / DateUtils.DAY_IN_MILLIS);
sDate += days+"d";
}
millisUntilFinished -= (days*DateUtils.DAY_IN_MILLIS);
if(millisUntilFinished > DateUtils.HOUR_IN_MILLIS)
{
hours = (int) (millisUntilFinished / DateUtils.HOUR_IN_MILLIS);
}
millisUntilFinished -= (hours*DateUtils.HOUR_IN_MILLIS);
if(millisUntilFinished > DateUtils.MINUTE_IN_MILLIS)
{
minutes = (int) (millisUntilFinished / DateUtils.MINUTE_IN_MILLIS);
}
millisUntilFinished -= (minutes*DateUtils.MINUTE_IN_MILLIS);
if(millisUntilFinished > DateUtils.SECOND_IN_MILLIS)
{
seconds = (int) (millisUntilFinished / DateUtils.SECOND_IN_MILLIS);
}
sDate += " "+String.format("%02d",hours)+":"+String.format("%02d",minutes)+":"+String.format("%02d",seconds);
tv.setText(sDate.trim());
}
#Override
public void onFinish() {
tv.setText("Finished");
}
};
counters.put(tv, cdt);
cdt.start();
return rowView;
}
public void cancelAllTimers()
{
Set<Entry<TextView, CountDownTimer>> s = counters.entrySet();
Iterator it = s.iterator();
while(it.hasNext())
{
try
{
Map.Entry pairs = (Map.Entry)it.next();
CountDownTimer cdt = (CountDownTimer)pairs.getValue();
cdt.cancel();
cdt = null;
}
catch(Exception e){}
}
it=null;
s=null;
counters.clear();
}
}
Please have a look here at my blog where you will find an example on how to achieve this.
One solution is to put the TextView that represents each counter into a HashMap together with it's position in the list as the key.
In getView()
TextView counter = (TextView) v.findViewById(R.id.myTextViewTwo);
if (counter != null) {
counter.setText(myData.getCountAsString());
// add the TextView for the counter to the HashMap.
mCounterList.put(position, counter);
}
Then you can update the counters by using a Handler and where you post a runnable.
private final Runnable mRunnable = new Runnable() {
public void run() {
MyData myData;
TextView textView;
// if counters are active
if (mCountersActive) {
if (mCounterList != null && mDataList != null) {
for (int i=0; i < mDataList.size(); i++) {
myData = mDataList.get(i);
textView = mCounterList.get(i);
if (textView != null) {
if (myData.getCount() >= 0) {
textView.setText(myData.getCountAsString());
myData.reduceCount();
}
}
}
}
// update every second
mHandler.postDelayed(this, 1000);
}
}
};
after checking few ways to do that this is a creative solution i wrote .its simple and works perfectly .
the idea it to check if the Runnable that updates the data is updating the same TextView and if the TextView is related to different view the Runnablewill stop and by this way there will be no extra Thread's in the background so there will be no blinking text's or memory leak.
1 . inside your getView() add each TextView tag with his position .
text = (TextView) view
.findViewById(R.id.dimrix);
text.setTag(position);
2 . create class that implements Runnable so we can pass parameters .
public class mUpdateClockTask implements Runnable {
private TextView tv;
final Handler mClockHandler = new Handler();
String tag;
public mUpdateClockTask(TextView tv,
String tag) {
this.tv = tv;
this.tag = tag;
}
public void run() {
if (tv.getTag().toString().equals(tag)) {
// do what ever you want to happen every second
mClockHandler.postDelayed(this, 1000);
}
}
};
so what happen here is unless the TextView is not equals to the original tag the Runnable will stop .
3 . go back to your getView()
final Handler mClockHandler = new Handler();
mUpdateClockTask clockTask = new mUpdateClockTask(text,
activeDraw, text.getTag().toString());
mClockHandler.post(clockTask);
that's it , work's perfect !
Here is another solution of using ListView with multiple CountDownTimer. Firstly, we create a class MyCustomTimer that holds the CountDownTimer:
public class MyCustomTimer{
public MyCustomTimer() {
}
public void setTimer(TextView tv, long time) {
new CountDownTimer(time, 1000) {
public void onTick(long millisUntilFinished) {
//Set formatted date to your TextView
tv.setText(millisUntilFinished);
}
public void onFinish() {
tv.setText("Done!");
}
}.start();
}
}
Then, initilize the created class in your adapter:
public class MyAdapter extends BaseAdapter {
private LayoutInflater mInflater;
private MyCustomTimer myTimer;
private ArrayList<Item> myItems;
public MyAdapter(Context context, ArrayList<Item> data) {
mInflater = LayoutInflater.from(context);
myTimer= new MyCustomTimer();
myItems = data;
}
//... implementation of other methods
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = mInflater.inflate(R.layout.listview_row, null);
TextView tvTimer = (TextView) convertView.findViewById(R.id.textview_timer);
TextView tvName = (TextView) convertView.findViewById(R.id.textview_name);
Item item = data.get(position);
tvName.setText(item.getName());
myTimer.setTimer(tvTimer, item.getTime());
return convertView;
}
}
I am making a time sheet program where a user inputs his in- and out-punches. I have a ListView that I am populating from an array of calendar objects. I would like each row to show the day and date then on a new line the time, but I only want to display the day and date if it is different from the previous element.
Currently, I am setting visibility in the BaseAdapter based on comparisons using position vs position-1 (which are used as indices to the array). This only works if the whole list fits on the screen. If it extends beyond the screen and the user scrolls around the results are unpredictable.
To further confuse things, I am setting the color of the times, based on the position, to alternate between green and red (in/out) and it works as expected, scrolling or not.
How does Android handle the ListView position when scrolling or what could I do differently to show/hide the day and date?
public class TimeSheetActivity extends Activity {
SQLiteDatabase timesDatabase;
Cursor punchCursor;
private static Calendar[] allPunches;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timesheet);
} //end onCreate()
#Override
public void onResume() {
super.onResume();
//Open database
timesDatabase = openOrCreateDatabase(
"times_database.db",
SQLiteDatabase.CREATE_IF_NECESSARY,
null);
timesDatabase.setLocale(Locale.getDefault());
timesDatabase.setLockingEnabled(true);
timesDatabase.setVersion(1);
punchCursor = timesDatabase.query("Timepunches", null, null, null, null, null, "punch ASC;");
updateTimeSheet();
} //end onResume()
#Override
public void onPause() {
super.onPause();
timesDatabase.close();
} //end onResume()
private static class EfficientAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public EfficientAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return allPunches.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.time_list_row, null);
holder = new ViewHolder();
holder.text1 = (TextView) convertView.findViewById(R.id.day_textview);
holder.text2 = (TextView) convertView.findViewById(R.id.date_textview);
holder.text3 = (TextView) convertView.findViewById(R.id.times_this_day_textview);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
String dayNames[] = new DateFormatSymbols().getWeekdays();
//Initialize first list element
if (position < 1) {
holder.text1.setText(dayNames[allPunches[position].get(Calendar.DAY_OF_WEEK)]);
holder.text2.setText(formatDate(allPunches[position]));
}
else {
holder.text1.setText(dayNames[allPunches[position].get(Calendar.DAY_OF_WEEK)]);
holder.text2.setText(formatDate(allPunches[position]));
holder.text1.setVisibility(View.VISIBLE);
holder.text2.setVisibility(View.VISIBLE);
//Hide day and date if same as last
if (formatDate(allPunches[position]).contentEquals(formatDate(allPunches[position-1]))) {
holder.text1.setVisibility(View.GONE);
holder.text2.setVisibility(View.GONE);
}
}
holder.text3.setText(formatTime(allPunches[position], true) + " " + position);
//Color in/out punches
if (position%2 == 0) {
holder.text3.setTextColor(Color.GREEN);
}
else {
holder.text3.setTextColor(Color.RED);
}
return convertView;
} //end getView()
static class ViewHolder {
public TextView text1;
TextView text2;
TextView text3;
}
} //end EfficientAdapter
public void updateTimeSheet() {
punchCursor = timesDatabase.query("Timepunches", null, null, null, null, null, "punch ASC;");
allPunches = new Calendar[punchCursor.getCount()];
int i = 0; //for indexing allPunches
Calendar nextDay = Calendar.getInstance();
nextDay.setLenient(true);
//populate allPunches
for (punchCursor.moveToFirst(); !punchCursor.isAfterLast(); punchCursor.moveToNext()) {
allPunches[i] = Calendar.getInstance();
allPunches[i].setTimeInMillis(punchCursor.getLong(0));
++i;
} //end for
final ListView timeSheetListView = (ListView)findViewById(R.id.timesheet_listview);
timeSheetListView.setAdapter(new EfficientAdapter(this));
timeSheetListView.setOnItemClickListener(new OnItemClickListener() {...}); //end click listener for list item
} //end updateTimeSheet()
public static String formatTime(Calendar thisTime, boolean showAMPM) {...}
public static String formatDate(Calendar thisDate) {
String formattedDate = "";
formattedDate += thisDate.get(Calendar.MONTH) +"-"+ thisDate.get(Calendar.DAY_OF_MONTH) +"-"+ thisDate.get(Calendar.YEAR);
return formattedDate;
} //end formatDate()
} //end TimeSheet Activity
The views in the ListView are resused as you scroll. This likely causes the odd behavior you see. The important thing to remember when overriding getView is to set the behavior explicitly every time. Don't depend on a a view being in a default state, since you may be reusing a view that has already been changed.
In your particular case, make sure that you always set the visiblity explicitly to true or gone.
Also, did you copy paste this code directly? I believe you are missing a closing bracket for your second else statement.
I seem to have been setting VISIBLE in the wrong place. Here is the code for getView() that seems to have it fixed!
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.time_list_row, null);
holder = new ViewHolder();
holder.text1 = (TextView) convertView.findViewById(R.id.day_textview);
holder.text2 = (TextView) convertView.findViewById(R.id.date_textview);
holder.text3 = (TextView) convertView.findViewById(R.id.times_this_day_textview);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
String dayNames[] = new DateFormatSymbols().getWeekdays();
holder.text1.setVisibility(View.VISIBLE);
holder.text2.setVisibility(View.VISIBLE);
//Initialize list
if (position < 1) {
holder.text1.setText(dayNames[allPunches[position].get(Calendar.DAY_OF_WEEK)]);
holder.text2.setText(formatDate(allPunches[position]));
}
else {
//Show day and date if not same as last
holder.text1.setText(dayNames[allPunches[position].get(Calendar.DAY_OF_WEEK)]);
holder.text2.setText(formatDate(allPunches[position]));
if (formatDate(allPunches[position]).contentEquals(formatDate(allPunches[position-1]))) {
holder.text1.setVisibility(View.GONE);
holder.text2.setVisibility(View.GONE);
}
}
holder.text3.setText(formatTime(allPunches[position], true));
//Color in/out punches
if (position%2 == 0) {
holder.text3.setTextColor(Color.GREEN);
}
else {
holder.text3.setTextColor(Color.RED);
}
return convertView;
} //end getView()
static class ViewHolder {
public TextView text1;
TextView text2;
TextView text3;
}
} //end EfficientAdapter