I have a RecyclerView. Each row has a play button, textview and Progressbar. when click on the play button have to play audio from my sdcard and have to progress Progressbar
The problem is when i scroll down the recyclerview change the Progressbar in next row.means I can fit 5 items on the screen at once. When I scroll to the 6th, 6th row seekbar changes suddenly.
public class ListAdapter extends RecyclerView.Adapter {
private List<Historyitem> stethitems;
public Context mContext;
public Activity activity;
public Handler mHandler;
static MediaPlayer mPlayer;
static Timer mTimer;
public ListAdapter(Activity activity,Context mContext,List<Historyitem> stethitems) {
this.stethitems = stethitems;
this.mContext = mContext;
this.activity = activity;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View rootView = LayoutInflater.
from(mContext).inflate(R.layout.stethoscopeadapteritem, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
rootView.setLayoutParams(lp);
mHandler = new Handler();
return new MyViewHolder(rootView);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
final Historyitem dataItem = stethitems.get(position);
final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
myViewHolder.progressplay.setProgress(0);
myViewHolder.stethdatetime.setText(dataItem.getReported_Time());
myViewHolder.stethhosname.setText(dataItem.getdiv());
if(dataItem.getPatient_Attribute().replaceAll(" ","").equals("")){
myViewHolder.stethdoctorname.setText(dataItem.getunit());
} else {
myViewHolder.stethdoctorname.setText(dataItem.getPatient_Attribute());
}
myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FileDownload(dataItem.getmsg(),
myViewHolder.progressplay);
}
});
}
#Override
public int getItemCount() {
return stethitems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
final CustomTextRegular stethdatetime;
final CustomTextView stethhosname;
final CustomTextBold stethdoctorname;
final ImageButton stethstreamplay;
final NumberProgressBar progressplay;
public MyViewHolder(View itemView) {
super(itemView);
stethdatetime = (CustomTextRegular)
itemView.findViewById(R.id.stethdatetime);
stethhosname = (CustomTextView)
itemView.findViewById(R.id.stethhosname);
stethdoctorname = (CustomTextBold)
itemView.findViewById(R.id.stethdoctorname);
stethstreamplay = (ImageButton)
itemView.findViewById(R.id.stethstreamplay);
progressplay= (NumberProgressBar)
itemView.findViewById(R.id.progressplay);
}
}
public void FileDownload(final String downloadpath,
final NumberProgressBar progressplay) {
new AsyncTask<NumberProgressBar, Integer, NumberProgressBar>() {
NumberProgressBar progress;
#Override
protected void onPreExecute() {
super.onPreExecute();
try {
if(mPlayer!=null){
mPlayer.stop();
}
}catch (Exception e){
}
try {
if(mTimer != null){
mTimer.purge();
mTimer.cancel();
}
}catch (Exception e){
}
}
#Override
protected NumberProgressBar doInBackground(NumberProgressBar... params) {
int count;
progress = progressplay;
try {
final List<NameValuePair> list = new ArrayList<NameValuePair>();
list.add(new BasicNameValuePair("pid",id));
URL url = new URL(Config.requestfiledownload + "?path=" +
downloadpath);
URLConnection connection = url.openConnection();
connection.connect();
int lenghtOfFile = connection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(Environment.getExternalStorageDirectory() +
"record.wav");
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
publishProgress((int) (total * 100 / lenghtOfFile));
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {
}
return progress;
}
#Override
protected void onPostExecute(final NumberProgressBar numberProgressBar) {
super.onPostExecute(numberProgressBar);
try {
StartMediaPlayer(numberProgressBar);
} catch (Exception e){
e.printStackTrace();
}
}
}.execute();
}
public void StartMediaPlayer(final NumberProgressBar progressbar){
Uri playuri = Uri.parse("file:///sdcard/record.wav");
mPlayer = new MediaPlayer();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.reset();
try {
mPlayer.setDataSource(mContext, playuri);
} catch (IllegalArgumentException e) {
} catch (SecurityException e) {
} catch (IllegalStateException e) {
} catch (Exception e) {
}
try {
mPlayer.prepare();
} catch (Exception e) {
}
mPlayer.start();
progressbar.setMax(mPlayer.getDuration());
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
if(mPlayer!=null) {
mPlayer.release();
progressbar.setProgress(0);
}
if(mTimer != null){
mTimer.purge();
mTimer.cancel();
}
}
});
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
progressbar.setProgress(mPlayer.getCurrentPosition());
}
});
}
},0,500);
}}
Please try this
If you are using ListView - override the following methods.
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
If you are using RecyclerView - override only getItemViewType() method.
#Override
public int getItemViewType(int position) {
return position;
}
Add setHasStableIds(true); in your adapter constructor and Override these two methods in adapter. It also worked if anyone using a RecyclerView inside a ViewPager which is also inside a NestedScrollView.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
As the name implies, the views in a RecyclerView are recycled as you scroll down. This means that you need to keep the state of each item in your backing model, which in this case would be a Historyitem, and restore it in your onBindViewHolder.
1) Create position, max, and whatever other variables you need to save the state of the ProgressBar in your model.
2) Set the state of your ProgressBar based on the data in your backing model; on click, pass the position of the item to your FileDownload/StartMediaPlayer methods.
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
final Historyitem dataItem = stethitems.get(position);
final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
myViewHolder.progressplay.setMax(dataItem.getMax());
myViewHolder.progressplay.setProgress(dataItem.getPosition());
...
myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FileDownload(dataItem.getmsg(), position);
}
});
3) Update the progress bar by updating the backing model and notifying that it was changed.
stethitems.get(position).setPosition(mPlayer.getCurrentPosition());
notifyItemChanged(position);
I have faced the same problem while I was trying to implement a recyclerview that contains a edittex and a checkbox as a row elements. I solved the scrolling value changing problem just by adding the following two lines in the adapter class.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
I hope it will be a possible solution. Thanks
recyclerview.setItemViewCacheSize(YourList.size());
If your recyclerview ViewHolder has more logic or has a different different view then you should try:
**order_recyclerView.setItemViewCacheSize(x);**
where x is the size of the list. The above works for me, I hope it works for you too.
When we are changing RecyclerView items dynamically (i.e. when changing background color of a specific RecyclerView item), it could change appearance of the items in unexpected ways when scrolling due to the nature of how RecyclerView reuse its items.
However to avoid that it is possible to use android.support.v4.widget.NestedScrollView wrapped around the RecyclerView and letting the NestedScrollView handle the scrolling.
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
And then in the code you can disable nested scrolling for the RecyclerView to smooth out scrolling by letting only the NestedScrollView to handle scrolling.
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
Just put you recylerView in a NestedScroll View in your xml and add the property nestedScrollingEnabled = false.
And on your adapter onBindViewHolder add this line
final MyViewHolder viewHolder = (MyViewHolder)holder;
Use this viewHolder object with your views to setText or do any kind of Click events.
e.g viewHolder.txtSubject.setText("Example");
Override the method getItemViewType in adapter. in kotlin use
override fun getItemViewType(position: Int): Int {
return position
}
I had the same problem while handle a lot of data , it works with 5 because it renders the five elements that are visible on the screen but that gives prob with more elements. The thing is ..
Sometimes RecyclerView and listView just skips Populating Data. In case of RecyclerView binding function is skipped while scrolling but when you try and debug the recyclerView adapter it will work fine as it will call onBind every time , you can also see the official google developer's view The World of listView. Around 20 min -30 min they will explain that you can never assume the getView by position will be called every time.
so, I will suggest to use
RecyclerView DataBinder created by satorufujiwara.
or
RecyclerView MultipleViewTypes Binder created by yqritc.
These are other Binders available if you find those easy to work around .
This is the way to deal with MultipleView Types or if you are using large amount of data . These binders can help you
just read the documentation carefully that will fix it, peace!!
Why don't you try like this,
HashMap<String, Integer> progressHashMap = new HashMap<>();
//...
if(!progressHashMap.containsKey(downloadpath)){
progressHashMap.put(downloadpath, mPlayer.getCurrentPosition());
}
progressbar.setProgress(progressHashMap.get(downloadpath));
try this
#Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) { LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return LinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
}; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); }
see this also
This line changes progress to 0 on each bind
myViewHolder.progressplay.setProgress(0);
Save its state somewhere then load it in this same line.
I had the similar issue and searched alot for the right answer. Basically it is more of a design of recycler view that it updates the view on the scroll because it refreshes the view.
So all you need to do is at the bind time tell it not to refresh it.
This is how your onBindViewHolder should look like
#Override
#SuppressWarnings("unchecked")
public void onBindViewHolder(final BaseViewHolder holder, final int position) {
holder.bind(mList.get(position));
// This is the mighty fix of the issue i was having
// where recycler view was updating the items on scroll.
holder.setIsRecyclable(false);
}
This is the expected behaviour of recyclerView. Since the view is recycled your items may get into random views. To overcome this you have to specify which item is put into which kind of view by yourself. This information can be kept in a SparseBooleanArray. what you can do is create a SparseBooleanArray in your adapter like this
SparseBooleanArray selectedItems = new SparseBooleanArray();
whenever your view changes, do:
selectedItems.put(viewItemIndex,true);
Now in your onBindViewHolder do
if(selectedItems.get(position, false)){
//set progress bar of related to the view to desired position
}
else {
//do the default
}
This is the basic to solve your problem. You can adjust this logic to any kind of similar problem in recyclerView.
Related
I have an Activity. I am creating a File which has some data when activity is created. All the files are properly created and data is correctly written.
Following is my code
public class MyRecordingsActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private RecordingsAdapter recordingsAdapter;
private ArrayList<Recording> recordingArrayList;
private File userRecordingFile;
private static final String USER_MIX_DIR = "UserMix";
private String lines[]=new String[]{};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_recordings);
for(int i=0;i<5;i++){
try{
userRecordingFile = new File(createRecordingFiles(), "Recording"+i+".txt");
FileWriter writer = new FileWriter(userRecordingFile);
writer.append("DEF"+i+"\nHIJ "+i);
writer.flush();
writer.close();
}
catch (Exception e){
e.printStackTrace();
}
}
getSupportActionBar().hide();
recordingArrayList=new ArrayList<>();
recyclerView=findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerView.setHasFixedSize(true);
readFiles();
Toast.makeText(getApplicationContext(),lines[0],Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(),lines[1],Toast.LENGTH_SHORT).show();
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[0],lines[1]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[2],lines[3]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[4],lines[5]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[6],lines[7]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[8],lines[9]));
recordingsAdapter=new RecordingsAdapter(recordingArrayList);
recyclerView.setAdapter(recordingsAdapter);
}
public File createRecordingFiles() {
File dirRoot = getExternalCacheDir();
File workDir = new File(dirRoot, USER_MIX_DIR);
//Toast.makeText(getApplicationContext(), "HI", Toast.LENGTH_SHORT).show();
if (!workDir.exists()) {
workDir.mkdirs();
File recordingFile = new File(workDir, "Recording File ");
try {
recordingFile.createNewFile();
} catch (IOException e) {
}
}
return workDir;
}
public void readFiles(){
StringBuilder text = new StringBuilder();
BufferedReader br=null;
try {
File dirRoot = getExternalCacheDir();
File workDir = new File(dirRoot, USER_MIX_DIR);
for(int i=0;i<5;i++){
File file = new File(workDir,"Recording"+i+".txt");
br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append('\n');
lines=text.toString().split("\n");
}
}
br.close() ;
}catch (IOException e) {
e.printStackTrace();
}
}
}
The Toast which i have written correcly displays DEF0 and HIJ 0, but they are not displayed in the recyclerview. Following is screenshot of the screen
Following is my Adapter Class
public class RecordingsAdapter extends RecyclerView.Adapter<RecordingsAdapter.RecyclerViewHolder> {
public static final int TYPE_HEAD=0;
public static final int TYPE_LIST=1;
private ArrayList<Recording> recordingArrayList;
public RecordingsAdapter(ArrayList<Recording> recordingArrayList) {
this.recordingArrayList = recordingArrayList;
}
#Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
RecyclerViewHolder recyclerViewHolder;
if(viewType == TYPE_LIST){
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_my_recordings,parent,
false);
recyclerViewHolder=new RecyclerViewHolder(view,viewType);
return recyclerViewHolder;
}else if(viewType == TYPE_HEAD){
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.head_layout,parent,
false);
recyclerViewHolder=new RecyclerViewHolder(view,viewType);
return recyclerViewHolder;
}
return null;
}
#Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
Recording recording;
if(holder.view_type==TYPE_LIST){
recording=recordingArrayList.get(position);
holder.imageView.setImageResource(recording.getImage_id());
holder.typeTextView.setText(recording.getTitle());
holder.dateTimeTextView.setText(recording.getDatetime());
}else if (holder.view_type == TYPE_HEAD){
holder.typeHeaderTextView.setText("TYPE");
holder.titleHeaderTextView.setText("TITLE");
holder.dateTimeHeaderTextView.setText("DATE/TIME");
}
}
#Override
public int getItemCount() {
return recordingArrayList.size();
}
#Override
public int getItemViewType(int position) {
if(position==0){
return TYPE_HEAD;
}
return TYPE_LIST;
}
public static class RecyclerViewHolder extends RecyclerView.ViewHolder{
int view_type;
ImageView imageView;
TextView typeTextView,dateTimeTextView;
TextView typeHeaderTextView,titleHeaderTextView,dateTimeHeaderTextView;
public RecyclerViewHolder(View itemView, int viewType) {
super(itemView);
if(viewType==TYPE_LIST){
typeTextView=itemView.findViewById(R.id.tv_cell_recording_recording_name);
imageView=itemView.findViewById(R.id.iv_cell_recordings);
dateTimeTextView=itemView.findViewById(R.id.tv_cell_recording_date_time);
view_type=1;
}else if(viewType==TYPE_HEAD){
typeHeaderTextView=itemView.findViewById(R.id.tv_type_head_layout);
titleHeaderTextView=itemView.findViewById(R.id.tv_title_head_layout);
dateTimeHeaderTextView=itemView.findViewById(R.id.tv_date_time_head_layout);
view_type=0;
}
}
}
}
DEf0 and HIJ 0 are not displayed in the recyclerview. I am not able to understand why they are not displaying in my recyclerview. I have no errors in my log. Any help would be greatly appreciated
You are doing it in wrong way....
You are adding five items in recyclerview, but consider first is header.
So this takes your first item as header, and you see other four items.
What you need to do is, pass dummy item as first item and then add your 5 items.
this way you can solve your problem.
You can try this way:
recordingArrayList.add(new Recording(R.drawable.ic_launcher,"", ""));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[0],lines[1]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[2],lines[3]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[4],lines[5]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[6],lines[7]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[8],lines[9]));
or you can also do it as below:
change Adapter code
#Override
public int getItemCount() {
return recordingArrayList.size() + 1;
}
#Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
Recording recording;
if(holder.view_type==TYPE_LIST){
recording=recordingArrayList.get(position - 1);
...
}else if (holder.view_type == TYPE_HEAD){
holder.typeHeaderTextView.setText("TYPE");
holder.titleHeaderTextView.setText("TITLE");
holder.dateTimeHeaderTextView.setText("DATE/TIME");
}
}
but you need to verify for empty list in this way
Your recycler view might be hidden under the toolbar. Give a top margin of 56dp.
android:layout_marginTop="56dp"
Try this your first position is set your Title.
Use this
#Override
public int getItemViewType(int position) {
return TYPE_LIST;
}
Instead of this
#Override
public int getItemViewType(int position) {
if(position==0){
return TYPE_HEAD;
}
return TYPE_LIST;
}
What worked for me is that first I remove from the array any null value (since the first element could be null) and then I add null value again. That way I'm avoiding of duplicating null value. I don't use "if" or any adapter implemented methods so I made my app more efficient.
My code is as follow:
myArray.remove(null); // here I remove any preview added null value as first (0 position) element
myArray.add(0, null); //now I add null as first element
//this way I am avoiding showing content with a null values
I hope this helps someone
The toast is displayed because your card is there, but it's hidden by the toolbar. You can add some margin to the RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="schemas.android.com/apk/res/android"
xmlns:app="schemas.android.com/apk/res-auto"
xmlns:tools="schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_marginTop:"?android:attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recyclerView" />
</RelativeLayout>
If you wants to add Header without passing first blank data from activity, than you need to add blank entry at first position inside your Adapter like this.
Replace this code inside your Adapter:
public RecordingsAdapter(ArrayList<Recording> recordingArrayList) {
this.recordingArrayList = recordingArrayList;
this.recordingArrayList.add(0,null);
}
I want my recycler view rows to be highlighted after a specific interval of time, say after 2 seconds.
Searched all over the internet but no luck so far.
How about, in the recycler adapter OnBindViewHolder method, put a [Handler.postDelayed](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long)) in which you set the specific time where you want the item to change.
Inside the runner that you pass in the handler, you put in a boolean flag to check if the row will have a different colour/behaviour + a notifyDataSetChanged() in the adapter. (You will have to change your data object to accomodate this new variable)
The question is not very clear. I had two questions in mind that I mentioned in the comment of the question.
Do you want to highlight some specific rows?
Do you want to toggle the highlight after each two seconds?
So I'm going for a general solution for both.
Let us assume the object you're populating in your each row is like the following.
public class ListItem {
int value;
boolean highlight = false;
}
The list of ListItem object can be inserted in an ArrayList to be populated in the RecyclerView. Here is your adapter which may look like this.
// Declare the yourListItems globally in your Activity
List<ListItem> yourListItems = new ArrayList<ListItem>();
populateYourListItems();
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class YourViewHolder extends RecyclerView.ViewHolder {
private final TextView valueTextView;
private final LinearLayout background;
public YourViewHolder(final View itemView) {
super(itemView);
valueTextView = (TextView) itemView.findViewById(R.id.value_text_view);
background = (LinearLayout) itemView.findViewById(R.id.background);
}
public void bindView(int pos) {
int value = yourListItems.get(pos).value;
boolean isHighlighted = yourListItems.get(pos).hightlight;
valueTextView.setText(value);
// Set the background colour if the highlight value is found true.
if(isHighlighted) background.setBackgroundColor(Color.GREEN);
else background.setBackgroundColor(Color.WHITE);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_activity_log, parent, false);
return new YourViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof YourViewHolder) {
YourViewHolder vh = (YourViewHolder) holder;
vh.bindView(position);
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public int getItemCount() {
if (yourListItems == null || yourListItems.isEmpty())
return 0;
else
return yourListItems.size();
}
#Override
public int getItemViewType(int position) {
return 1;
}
}
Now when you want to highlight some specific items of your RecyclerView you need to just set the highlight value to true and then call notifyDataSetChanged() to bring into the change in effect.
So you might need a timer like the following which will highlight your rows as per your demand in every two seconds.
// Declare the timer
private Timer highlightTimer;
private TimerTask highlightTimerTask;
highlightTimer = new Timer();
highlightTimerTask = new TimerTask() {
public void run() {
highLightTheListItems();
}
};
highlightTimer.schedule(highlightTimerTask, 2000);
Now implement your highLightTheListItems function as per your need.
public void highLightTheListItems() {
// Modify your list items.
// Call notifyDataSetChanged in your adapter
yourAdapter.notifyDataSetChanged();
}
Hope that helps. Thanks.
Do you mean highlight as in colour the row's background? If so, you could do this in your listViewAdapter
#Override
public View getView(final int position, View row, ViewGroup parent){
if (row==null){
row = LayoutInflater.from(getContext()).inflate(mResource, parent, false);
}
if(foo){
row.setBackgroundColor(getResources().getColor(R.color.translucent_green));
}
else row.setBackgroundColor(Color.TRANSPARENT);
return row;
}
Then in colours.xml
<color name="translucent_green">#667cfc00</color>
The first 2 numbers(66) is the alpha value, ie opacity. The next 6 are RBG in hexadecimal.
I'm trying to get JSON data to display in a RecyclerView list, but whenever I try to make the call it seems that the RecyclerView comes up empty. I have it set up so that I get the API service/manager in onCreate prior to the configViews() method which I wrote. Am I making the call too early? I thought the problem would be to create the views/adapter prior to the call, but it doesn't seem to be making a difference.
This is the code for the Retrofit call:
listCall.enqueue(new Callback<List<Character>>() {
#Override
public void onResponse(Call<List<Character>> call, Response<List<Character>> response) {
if(response.isSuccessful()){
List<Character> characterList = response.body();
Log.v(TAG, response.toString());
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
configViews();
characterAdapter.notifyDataSetChanged();
}
else {
int sc = response.code();
}
}
#Override
public void onFailure(Call<List<Character>> call, Throwable t) {
}
});
And that configViews() method is here, albeit simpler than when I first started (I have been moving bits around to test whether they will affect inflation of the RecyclerView):
private void configViews() {
characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
EDIT: Thank you all for your replies! As requested here is the Adapter class:
public class CharacterAdapter extends
RecyclerView.Adapter<CharacterAdapter.Holder> {
private static final String TAG = CharacterAdapter.class.getSimpleName();
private final CharacterClickListener clickListener;
private List<Character> characters;
//Constructor for CharacterAdapter.
public CharacterAdapter(CharacterClickListener listener){
characters = new ArrayList<>();
clickListener = listener;
}
//Inflates CardView layout
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View row =
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, parent,
false);
return new Holder(row);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
Character currentCharacter = characters.get(position);
holder.name.setText(currentCharacter.getName());
holder.description.setText(currentCharacter.getDescription());
//Picasso loads image from URL
Picasso.with(holder.itemView.getContext())
.load("http://gateway.marvel.com/"+
currentCharacter.getThumbnail()).into(holder.thumbnail);
}
#Override
public int getItemCount() {
return characters.size();
}
public void addCharacter(Character character) {
//Log.d(TAG, character.getThumbnail());
characters.add(character);
notifyDataSetChanged();
}
public Character getSelectedCharacter(int position) {
return characters.get(position);
}
public class Holder extends RecyclerView.ViewHolder implements
View.OnClickListener{
//Holder class created to be implemented by adapter.
private ImageView thumbnail;
private TextView name, description;
public Holder(View itemView) {
super(itemView);
thumbnail = (ImageView)
itemView.findViewById(R.id.character_thumbnail);
name = (TextView) itemView.findViewById(R.id.character_name);
description = (TextView)
itemView.findViewById(R.id.character_description);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
clickListener.onClick(getLayoutPosition());
}
}
public interface CharacterClickListener {
void onClick(int position);
}
}
In principle you have done the right thing. You can instantiate the views before and fire a fetch request. Once you receive the response, you can notify the adapter that the datatset has changed and the adapter will refresh your views.
The only correction here is, you need to call your configViews(); before the for loop. So your code should be like -
configViews();
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
characterAdapter.notifyDataSetChanged();
Because right now what is happening is you add all your data using add character and then after that you again call config views which reinitialises your adapter at
characterAdapter = new CharacterAdapter(this);
Hence the empty views.
PS: I don't know your adapter's addCharacter method but i am hoping it is doing the right job. If it still doesn't work let me know and then add your addCharacter code as well.
Based on code the snippets, in configViews() method you are creating new instance of CharacterAdapter. The following code snippets will work.
private void configViews() {
//characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
You should leave the call configViews() call in onCreate but move the rest of it into onResume. It'll achieve following:
Ensure views are ready to display data
Refresh data if your app was brought to background and re-opened
Hi I got frustrated while searching solution for my problem.My problem is that i have a gridview to show all images which i selected from gallery.I want to display progressbar on each images in gridview.and while uploading images to server using multipart i want too update progressbar..
I displayed progressbar on each imageview but i am unable to show progress on each progressbar.
SO please help me to show how to show progress bar and their respective process on each imageview.
thanks in advance
Create a interface for an observer:
interface ProgressListener {
void onProgressUpdate(String imagePath, int progress);
}
Let the view holder implement that observer and know the image path:
public class ViewHolder implements ProgressListener {
ImageView imgQueue;
ProgressBar pb;
TextView tv;
String imagePath; //set this in getView!
void onProgressUpdate(String imagePath, int progress) {
if (!this.imagePath.equals(imagePath)) {
//was not for this view
return;
}
pb.post(new Runnable() {
pb.setProgress(progress);
});
}
//your other code
}
The adapter shall hold an map of observers for a particular image path/uri whatever and have an method that is called by the upload/download task. Also add methods to add and remove observer:
public class SelectedAdapter_Test extends BaseAdapter {
private Map<String, ProgressListener> mProgressListener = new HashMap<>();
//your other code
synchronized void addProgressObserver(String imagePath, ProgressListener listener) {
this.mListener.put(imagePath, listener);
}
synchronized void removeProgressObserver(String imagePath) {
this.mListener.remove(imagePath);
}
synchronized void updateProgress(String imagePath, int progress) {
ProgressListener l = this.mListener.get(imagePath);
if (l != null) {
l.onProgressUpdate(imagePath, progress);
}
}
//other code
}
In getView of the adapter register the view holder as an observer:
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
holder.imagePath = data.get(i).getSdcardPath();
this.addProgressObserver(holder.imagePath, holder);
return convertView;
}
The problem right now is, that we register the observer but don't unregister. So let the adapter implement the View.addOnAttachStateChangeListener:
public class SelectedAdapter_Test extends BaseAdapter implements View.addOnAttachStateChangeListener {
//other code
void onViewAttachedToWindow(View v) {
//We ignore this
}
void onViewDetachedFromWindow(View v) {
//View is not visible anymore unregister observer
ViewHolder holder = (ViewHolder) v.getTag();
this.removeProgressObserver(holder.imagePath);
}
//other code
}
Register that observer when you return the view.
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
convertView.addOnAttachStateChangeListener(this);
return convertView;
}
Finally you are able to tell the views what the progress is:
#Override
public void transferred(long num) {
int progress = (int) ((num / (float) totalSize) * 100);
selectedAdapter.onProgressUpdate(listOfPhotos.get(i).getSdcardPath(), progress);
}
One final problem remains, what if the activity is gone while the upload is in progress? You need to check if the activity is still alive. Maybe setting a flag in the adapter to true in onCreate and to false in onDestroy would do the trick. Then the last code fragment could check that flag and not notify the adapter on changes anymore.
So thats basically the idea of how to solve this. Does it work? I don't know I wrote it from scratch without any testing. And even if it does, you still have to manage the states when the progress is 0 or 100. But I leave that to you. Also you might want to change the BaseAdapter for an recyclerView so that we can get rid of the View.addOnAttachStateChangeListener.
add boolean in adapter class
public SelectedAdapter_Test(Context c, ArrayList<CustomGallery> data, boolean showProgress) {
mContext = c;
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.data = data;
this.showProgress = showProgress;
}
changes in Adapter class getView
holder.pb = (ProgressBar) convertView.findViewById(R.id.progressbar);
if (showProgress)
holder.pb.setVisibility(View.VISIBLE);
else
holder.pb.setVisibility(View.GONE);
make changes in doFileUpload
private void doFileUpload(View v) {
View vi = v;
for (i = 0; i < listOfPhotos.size(); i++) {
<--your task-->
}
//**important**
SelectedAdapter_Test mTest = new SelectedAdapter_Test(context,data,false);
// set above adapter object respectively;
mList.setadapter(mTest);
}
FYI. pass showProgress value as true for the first time when you set adapter.
I have a weird problem. I switched to RecyclerView from ListView and I can't refresh or notify of change in my ListView. I tried calling Item.this.notifyDataSetChanged();
and other methods to refresh View but it doesn't work.
Instead RecyclerView is refreshed when I scroll(regardless of direction). How can I notify my RecyclerView when there is a change?
CODE:
#Override
public void onBindViewHolder(Ids holder, final int position) {
rowItemClass = (ListViewRow) rowItems.get(position);
Log.e("swag", "OYOYOYOYOYO");
if (Globals.isPlaying && Globals.pos == position) {
if (pausedSamePos == true) {
holder.pauseed_play.setVisibility(View.VISIBLE);
holder.playing_pause.setVisibility(View.GONE);
} else {
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.VISIBLE);
}
holder.song_currenttime_sb.setActive();
holder.song_duration.setVisibility(View.INVISIBLE);
holder.song_duration_sb.setVisibility(View.VISIBLE);
holder.seekbar.setActive();
} else {
holder.seekbar.setInactive();
holder.song_currenttime_sb.setInactive();
holder.song_icon.setImageResource(rowItemClass.getImageId());
holder.song_duration_sb.setVisibility(View.INVISIBLE);
holder.song_duration.setVisibility(View.VISIBLE);
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.GONE);
}
sharedPreference = new SharedPreference();
holder.song_duration.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_duration_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setTypeface(Globals.getTypefacePrimary(context));
holder.song_currenttime_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setText(rowItemClass.getTitle());
holder.song_duration.setText(rowItemClass.getDesc());
holder.song_duration_sb.setText(rowItemClass.getDesc());
holder.favorite.setTag(position);
holder.song_currenttime_sb.setTag(position);
holder.seekbar.setTag(position);
holder.clickRegister.setTag(position);
holder.song_icon.setTag(position);
holder.song_name.setTag(position);
holder.song_duration.setTag(position);
holder.song_duration_sb.setTag(position);
holder.more_options.setTag(position);
// int task_id = (Integer) holder.seekbar.getTag();
final Ids finalHolder = holder;
holder.clickRegister.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
if ((Globals.isPlaying.booleanValue())
&& (Globals.pos == position)) {
pausePlaying();
} else {
Globals.stopPlaying();
pausedSamePos = false;
Globals.pos = position;
Globals.isPlaying = true;
Item.this.notifyDataSetChanged();
Globals.mp = MediaPlayer.create(context, Integer
.valueOf(Item.this.songPos[position])
.intValue());
Globals.mp.start();
Globals.pos = position;
Globals.isPlaying = Boolean.valueOf(true);
Item.this.notifyDataSetChanged();
Globals.mp
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(
MediaPlayer mpOnComplete) {
mpOnComplete.release();
Globals.isPlaying = false;
pausedSamePos = false;
Globals.isPlaying = Boolean
.valueOf(false);
finalHolder.menu_options
.startAnimation(new ViewExpandAnimation(
finalHolder.menu_options));
Item.this.notifyDataSetChanged();
}
});
}
} catch (Exception localException) {
}
}
});
holder.clickRegister
.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Globals.stopPlaying();
Item.this.notifyDataSetChanged();
return true;
}
});
}
I've been struggling hard with a similar issue, trying to handle a use-case where all of the adapter's content has to be replaced and the recycler-view should start from scratch: calling notifyDataSetChanged(), swapAdapter() with numerous combinations of subsequent calls to view/layout-manager invalidation requests resulted in nothing but a (seemingly) empty recycler-view. The view didn't even try to rebind the view holders.
What seemed to have worked it out is this hack-ish fix:
view.swapAdapter(sameAdapter, true);
view.scrollBy(0, 0);
As it turns out, scrollBy (even with 0 offsets) drives the recycler-view into laying out its views and executing the pending view-holders rebinding.
if you want notify your recycleListView just simple call notifyDataSetChanged(); in your class adapter. this is my method in my adapter class :
public class ListAdapter extends RecyclerView.Adapter<Listdapter.ViewHolder> {
....
private List<DesaObject> desaObjects= new ArrayList<DesaObject>();
public void setListObjects(List<DesaObject> desaObjects){
if(this.desaObjects.size()>0)
this.desaObjects.clear();
this.desaObjects = desaObjects;
notifyDataSetChanged();
}
....
public static class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final AppCompatTextView desa;
public ViewHolder(View view) {
super(view);
mView = view;
desa = (AppCompatTextView) view.findViewById(R.id.desa);
}
}
}
in your code you dont actually set or change your listObjects in the OnClickListener and please try notifyDataSetChanged(); instead of Item.this.notifyDataSetChanged();
I am not sure why is this, but notifyDataSetChanged(); didn't work. So I tried keeping track of changed items and refreshing them manually with notifyItemChanged(int); and so far it seems to be working. I am still not sure why refreshing whole RecyclerView didn't work.
Initialize your Adapter Again with changed data and use method 'swapadapter'. hope it helps.