I have a music player app where when an item is clicked it changes its state to 'play' state where it gets expanded to show a seek bar and other changes also take place and vice versa.
The problem is whenever I scroll up or down, the view holder swaps its current state(playing, paused visibility in this case) with some other random view in the list. It is also worth mentioning that this is consistent throughout the list so after every certain number of view holders, the state is the same as the one that was clicked (so if view at position 0 is in play visibility state after every 10 views a view is in the same state, note however the song still plays for the right one).
Here is the Adapter code (extends from a base adapter and the methods for play/pause visibility can be found here which are used in the song fragment code below):
public class AllSongsAdapter extends BaseSongAdapter<AllSongsAdapter.AllSongsItemHolder> {
private ArrayList<Song> songList;
public AllSongsAdapter(){
super(null);
}
//OnCreateViewHolder was called for every view;
//FIX: return 0 for same type of views.
#Override
public int getItemViewType(int position) {
return 0;
}
//cursor moves to the appropriate position in the list so we just have to update our views
#Override
public void onBindViewHolder(AllSongsItemHolder holder, Cursor cursor) {
if(cursor!=null) {
int i = cursor.getPosition();
Log.d("on Bind", "i:" + i);
Song songItem = songList.get(i);
holder.songItemName.setText(songItem.title);
holder.songItemArtistName.setText(songItem.artistName);
holder.songItemAlbumName.setText(songItem.albumName);
}
}
#Override
public void swapCursor(Cursor newCursor) {
super.swapCursor(newCursor);
songList = SongsLoader.getSongsForCursor(newCursor);
}
#Override
public AllSongsItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.d("CREATE VIEW HOLDER", "holder" );
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.songs_item_list, parent, false);
return new AllSongsItemHolder(v);
}
private Uri getAlbumArtUri(long albumId) {
return ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId);
}
public void setPlayVisibility(View child, RecyclerView rv) {
//View v = rv.getChildAt(rv.getChildAdapterPosition(child));
AllSongsItemHolder holder = (AllSongsItemHolder) rv.getChildViewHolder(child);
// if(getItemId(position) == rv.getChildItemId(v)){}
holder.seekBar.setVisibility(View.VISIBLE);
holder.songItemTimer.setVisibility(View.VISIBLE);
holder.songItemImage.setScaleType(ImageView.ScaleType.CENTER);
holder.songItemImage.setBackgroundColor(Color.parseColor("#303F9F"));
}
public void setPauseVisibility(View child, RecyclerView rv) {
//View v = rv.getChildAt(rv.getChildAdapterPosition(child));
AllSongsItemHolder holder = (AllSongsItemHolder) rv.getChildViewHolder(child);
//if(getItemId(position) == rv.getChildItemId(v)){}
holder.seekBar.setVisibility(View.GONE);
holder.songItemTimer.setVisibility(View.GONE);
holder.songItemImage.setScaleType(ImageView.ScaleType.FIT_XY);
holder.songItemImage.setBackgroundColor(0);
}
static class AllSongsItemHolder extends RecyclerView.ViewHolder {
private ImageView songItemImage, songItemOptionDropDown;
private TextView songItemName, songItemAlbumName, songItemArtistName;
private View separator;
private SeekBar seekBar;
private ImageView nowPlayingIcon;
private TextView songItemTimer;
public AllSongsItemHolder(View v) {
super(v);
songItemImage = v.findViewById(R.id.songItemImage);
songItemOptionDropDown = v.findViewById(R.id.songItemOptionDropDown);
songItemAlbumName = v.findViewById(R.id.songItemAlbumName);
songItemArtistName = v.findViewById(R.id.songItemArtistName);
songItemName = v.findViewById(R.id.songItemName);
separator = v.findViewById(R.id.separator);
seekBar = v.findViewById(R.id.seekbar);
songItemTimer = v.findViewById(R.id.songItemTimer);
// nowPlayingIcon = v.findViewById(R.id.nowPlayingIcon);
}
}
}
I noticed that when getItemViewType returns position this problem does not occur because recycler view holds an instance for every item in the list. But clearly this is not an adequate solution because it slows down the scrolling when first loaded as it has to create every view. Could be something to do with this?
BaseAdapter code:
public abstract class BaseSongAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
protected Cursor mCursor;
protected int mRowIDColumn;
protected boolean mDataValid;
public BaseSongAdapter(Cursor c) {
init(c);
}
public BaseSongAdapter() {}
void init(Cursor c) {
boolean cursorPresent = c != null;
mCursor = c;
mDataValid = cursorPresent;
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
setHasStableIds(true);
if(mDataValid && mCursor!=null) {
Log.d("VALID", "CURSOR");
}
}
#Override
public void onBindViewHolder(VH holder, int position) {
//Log.d("ON BIND","CALLED");
if (!mDataValid) {
throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state.");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("Could not move cursor to position " + position + " when trying to bind viewholder");
}
onBindViewHolder(holder, mCursor);
}
public abstract void onBindViewHolder(VH holder, Cursor cursor);
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
if (mDataValid) {
return mCursor.getCount();
} else {
return 0;
}
}
#Override
public long getItemId(int position) {
if (!mDataValid) {
throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("Could not move cursor to position " + position + " when trying to get an item id");
}
return mCursor.getLong(mRowIDColumn);
}
public void swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return;
}
//Log.d("TAG", "swapCursor");
//Cursor oldCursor = mCursor;
if (newCursor != null) {
mCursor = newCursor;
mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataSetChanged();
} else {
mCursor = null;
mRowIDColumn = -1;
mDataValid = false;
// notify the observers about the lack of a data set
notifyItemRangeRemoved(0, getItemCount());
}
//return oldCursor;
}
}
Song Fragment code where clicks are handled(in onActivityCreated):
public class AllSongsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private final int LOADER_ID_ALLSONGS = 0;
private Context ctx;
private AllSongsAdapter mAdapter;
private MediaPlayerHolder mediaPlayerHolder;
//on click play,pause variables
private boolean playingCLicked = false;
private boolean firstTime = true;
private String playbackState;
private View lastChildView;
private long currentSongId;
private long newID;
private long nextID; //id for next song
private int lastTrackPosition; //for auto next track
private RecyclerView rv;
public AllSongsFragment() {
super();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
ctx = context;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID_ALLSONGS, null, this);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.all_songs_fragment, container, false);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
rv = (RecyclerView) getActivity().findViewById(R.id.fragmentList);
mediaPlayerHolder = new MediaPlayerHolder(getActivity());
mediaPlayerHolder.setPlaybackInfoListener(new PlaybackListener());
rv.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), rv, new ClickListener() {
#Override
public void click(View view, final int position) {
Toast.makeText(getActivity(), "onClick " + position, Toast.LENGTH_SHORT).show();
newID = mAdapter.getItemId(position);
if (playingCLicked) {
if (mediaPlayerHolder.isPlaying()) {
if (currentSongId == newID) {
mAdapter.setPauseVisibility(view, rv);
lastChildView = view;
}
//PAUSE IT
mediaPlayerHolder.pause();
Log.d("playingclicked", "true" + "state" + playbackState);
//when a different song is clicked while current song is playing
if (playbackState.equalsIgnoreCase("paused") && newID != currentSongId) {
currentSongId = newID;
mAdapter.setPauseVisibility(lastChildView, rv);
mAdapter.setPlayVisibility(view, rv);
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
mediaPlayerHolder.play();
lastTrackPosition = position;
lastChildView = view;
Log.d("else 1 positions", "currentsongID" + currentSongId + "lasttrack" + lastTrackPosition);
}
playingCLicked = !playingCLicked;
} else { //media is not playing
Log.d("else 1", "play");
if (firstTime) {
Log.d("different", "no");
currentSongId = newID;
lastTrackPosition = position;
lastChildView = view;
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
Log.d("SelectedTrack", "" + mediaPlayerHolder.getMediaPlayer().getSelectedTrack(MEDIA_TRACK_TYPE_AUDIO));
firstTime = false;
}
if (newID != currentSongId) {
Log.d("different", "yes");
//mediaPlayerHolder.stop();
currentSongId = newID;
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
firstTime = true;
}
if (currentSongId == newID)
mAdapter.setPlayVisibility(view, rv);
//PLAY IT
mediaPlayerHolder.play();
playingCLicked = !playingCLicked;
}
} else //----------playingclicked = false, first called--------------
{
if (!mediaPlayerHolder.isPlaying()) {
lastChildView = view;
mAdapter.setPlayVisibility(view, rv);
if (firstTime) {
Log.d("different", "no, first time");
currentSongId = newID;
lastTrackPosition = position;
lastChildView = view;
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
Log.d("SelectedTrack", "" + mediaPlayerHolder.getMediaPlayer().getSelectedTrack(MEDIA_TRACK_TYPE_AUDIO));
firstTime = false;
}
Log.d("playbackState", playbackState + "currentId " + currentSongId);
//called when current song is paused and after playingClicked = true following is called if songId is different
if (newID != currentSongId) {
Log.d("different", "yes");
currentSongId = newID;
mediaPlayerHolder.stop();
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
}
//PLAY IT
mediaPlayerHolder.play();
playingCLicked = !playingCLicked;
} else { //media is playing
Log.d("else 2", "pause");
if (newID == currentSongId) {
lastChildView = view;
lastTrackPosition = position;
mAdapter.setPauseVisibility(view, rv);
}
//PAUSE IT
mediaPlayerHolder.pause();
firstTime = false;
//when a different song is clicked while current song is playing
if (playbackState.equalsIgnoreCase("paused") && newID != currentSongId) {
currentSongId = newID;
mAdapter.setPauseVisibility(lastChildView, rv);
mAdapter.setPlayVisibility(view, rv);
mediaPlayerHolder.reset();
mediaPlayerHolder.loadMedia(currentSongId);
mediaPlayerHolder.play();
lastTrackPosition = position;
lastChildView = view;
Log.d("else 2 positions", "currentsongID" + currentSongId + "lasttrack" + lastTrackPosition);
}
playingCLicked = !playingCLicked;
}
}
}
#Override
public void onLongClick(View view, int position) {
Toast.makeText(getActivity(),"onLongClick " + position,Toast.LENGTH_SHORT).show();
}
}));
mAdapter = new AllSongsAdapter();
rv.setAdapter(mAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
rv.setLayoutManager(layoutManager);
//------------Temporary divider-----------------
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(getActivity(), layoutManager.getOrientation());
rv.addItemDecoration(itemDecoration);
}
#Override
public Loader onCreateLoader(int id, Bundle args) {
Log.d("CREATE LOADER", "SUCCESS");
Uri musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
final String[] PROJECTION = {"_id", "title", "artist", "album", "duration", "track", "artist_id", "album_id"};
return new CursorLoader(ctx,
musicUri,
PROJECTION,
null,
null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER );
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
Log.d("LOAD FINISHED", "SUCCESS");
}
#Override
public void onLoaderReset(Loader loader) {
Log.d("LOADER RESET", "CALLED");
mAdapter.swapCursor(null);
}
class PlaybackListener extends PlaybackStateListener{
#Override
void onDurationChanged(int duration) {
super.onDurationChanged(duration);
}
#Override
void onPositionChanged(int position) {
super.onPositionChanged(position);
}
#Override
void onStateChanged(int state) {
playbackState = PlaybackListener.convertStateToString(state);
}
#Override
void onPlaybackCompleted() {
//super.onPlaybackCompleted();
//pause visibility after completed
mAdapter.setPauseVisibility(lastChildView, rv);
}
#Override
void onLogUpdated(String formattedMessage) {
super.onLogUpdated(formattedMessage);
}
}
//---------------RECYCLER VIEW TOUCH EVENTS LISTENER CLASS------------------------
class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {
private GestureDetector gestureDetector;
private ClickListener mListener;
public RecyclerTouchListener(Context ctx, final RecyclerView recyclerView, ClickListener clickListener){
mListener = clickListener;
gestureDetector = new GestureDetector(ctx, new GestureDetector.OnGestureListener() {
#Override
public boolean onDown(MotionEvent e) {
return false;
}
#Override
public void onShowPress(MotionEvent e) {
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
// Log.d("GESTURE DETECTED", "ACTION UP" + e);
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
#Override
public void onLongPress(MotionEvent e) {
//Log.d("GESTURE DETECTED", "LONG PRESS");
if(rv.getScrollState() == SCROLL_STATE_IDLE && !(rv.getScrollState() == SCROLL_STATE_DRAGGING)) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null && mListener != null) {
mListener.onLongClick(child, recyclerView.getChildLayoutPosition(child));
}
}
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
//Log.d("Intercept Event", "INTERCEPTING \n" + e);
if(rv.getScrollState() == SCROLL_STATE_IDLE && !(rv.getScrollState() == SCROLL_STATE_DRAGGING)) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null && mListener != null && gestureDetector.onTouchEvent(e)) {
mListener.click(child, rv.getChildLayoutPosition(child));
}
}
//true if onTouchEvent is to be called, false if you want gesture detector to handle events
return false;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
}
Any help would be greatly appreciated!!
You need to be setting the play/pause/whatever visibility in onBindViewHolder or else it will be whatever the last setting was even though a new model object is bound to that view.
Related
I am currently working on an app, that finds all MP3s on a users phone and then puts them into a list. This works very fine and is very quick, even with many songs. Now I populate a new list with an object for each item of the list to then display it inside my recyclerview. The problem is, that I have 700+ songs on my phone and this blocks the UI thread quite some time.
Now, I want to use the recyclerview to not load all items from the list into the objects all at once but rather only when they are about to be displayed - but I have NO clue over how to do this. Right now, all objects are build and then displayed in a very long scrollview from the recyclerview after the UI thread has been blocked for a good 30 seconds. Can please anyone help me? Here is my code:
namespace Media_Player
{
[Activity(Label = "Media_Player", MainLauncher = true)]
public class MainActivity : Activity
{
static public MediaPlayer mediaPlayer;
List<MP3object> mp3;
MediaMetadataRetriever reader;
public static Button btn_StartOrPause, btn_Stop;
public static TextView txt_CurrentSong;
public static bool stopIsActive = false, firstStart = true;
public static Android.Net.Uri CurrentActiveSongUri;
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
PhotoAlbumAdapter mAdapter;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.test);
reader = new MediaMetadataRetriever();
PopulateMP3List(ReturnPlayableMp3(true));
mediaPlayer = new MediaPlayer();
InitRecView();
}
private void InitRecView()
{
// Instantiate the adapter and pass in its data source:
mAdapter = new PhotoAlbumAdapter(mp3);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter(mAdapter);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.SetLayoutManager(mLayoutManager);
}
private void PopulateMP3List(List<string> content)
{
mp3 = new List<MP3object>();
foreach (string obj in content)
{
WriteMetaDataToFileList(obj);
}
}
void WriteMetaDataToFileList(string obj)
{
reader.SetDataSource(obj);
//Write Mp3 as object to global list
MP3object ob = new MP3object();
{
if(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != null)
{
ob.SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
}
else
{
ob.SongName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != null)
{
ob.ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
}
else
{
ob.ArtistName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != null)
{
ob.AlbumName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum);
}
else
{
ob.AlbumName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
ob.Mp3Uri = obj; // can never be unknown!
ob.DurationInSec = int.Parse(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyDuration)) / 1000; // can never be unknown, div by 1000 to get sec not millis
}
mp3.Add(ob);
}
public List<string> ReturnPlayableMp3(bool sdCard)
{
List<string> res = new List<string>();
string phyle;
string path1 = null;
if(sdCard) // get mp3 from SD card
{
string baseFolderPath = "";
try
{
bool getSDPath = true;
Context context = Application.Context;
Java.IO.File[] dirs = context.GetExternalFilesDirs(null);
foreach (Java.IO.File folder in dirs)
{
bool IsRemovable = Android.OS.Environment.InvokeIsExternalStorageRemovable(folder);
bool IsEmulated = Android.OS.Environment.InvokeIsExternalStorageEmulated(folder);
if (getSDPath ? IsRemovable && !IsEmulated : !IsRemovable && IsEmulated)
baseFolderPath = folder.Path;
}
}
catch (Exception ex)
{
Console.WriteLine("GetBaseFolderPath caused the following exception: {0}", ex);
}
string xy = baseFolderPath.Remove(18); // This is result after this, but this hard coded solution could be a problem on different phones.: "/storage/05B6-2226/Android/data/Media_Player.Media_Player/files"
path1 = xy;
// path to SD card and MUSIC "/storage/05B6-2226/"
}
else // get Mp3 from internal storage
{
path1 = Android.OS.Environment.ExternalStorageDirectory.ToString();
}
var mp3Files = Directory.EnumerateFiles(path1, "*.mp3", SearchOption.AllDirectories);
foreach (string currentFile in mp3Files)
{
phyle = currentFile;
res.Add(phyle);
}
return res;
}
}
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView Caption { get; private set; }
public PhotoViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
Image = itemView.FindViewById<ImageView>(Resource.Id.imageView);
Caption = itemView.FindViewById<TextView>(Resource.Id.textView);
}
}
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public List<MP3object> mp3;
public PhotoAlbumAdapter(List<MP3object> mp3)
{
this.mp3 = mp3;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.lay, parent, false);
PhotoViewHolder vh = new PhotoViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
vh.Caption.Text = mp3[position].SongName;
}
public override int ItemCount
{
get { return mp3.Count(); }
}
}
}
So getting the list of strings with the locations of the Mp3 works very quickly, but then "WriteMetaDataToFileList(obj)" kicks in, comming from "PopulateMP3List(List content)" and this is what takes so long. What I think I need is for the recyclerview to only build the first 20 objects, and when the user starts scrolling, builds the next 20 objects and attaches them to list for them to also be scrolled. Please help me out here :)
Here is an abstract class:
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
private LinearLayoutManager linearLayoutManager;
protected PaginationScrollListener(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = linearLayoutManager.getChildCount();
int totalItemCount = linearLayoutManager.getItemCount();
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
and In your adapter you must follow this pattern:
public class ConsultancyAdapter extends RecyclerView.Adapter<ConsultancyAdapter.ConsultancyVH> {
private static final int ITEM = 0;
private static final int LOADING = 1;
private boolean isLoadingAdded = false;
public ConsultancyAdapter(List<Consultancy> consultancies, ConsultancyAdapterListener listener) {
}
#NonNull
#Override
public ConsultancyVH onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, layoutInflater);
break;
case LOADING:
View v2 = layoutInflater.inflate(R.layout.item_progress, parent, false);
viewHolder = new ConsultancyVH(v2);
break;
}
return (ConsultancyVH) viewHolder;
}
#NonNull
private RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
RecyclerView.ViewHolder viewHolder;
View v1 = inflater.inflate(R.layout.item_consultancy, parent, false);
viewHolder = new ConsultancyVH(v1);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ConsultancyVH holder, int position) {
Consultancy consultancy = consultancies.get(position);
switch (getItemViewType(position)) {
case ITEM:
ConsultancyVH mySingeCounseller = holder;
holder.title.setText(consultancy.getTitle()); // set cardTitle
holder.fieldArea.setText(consultancy.getField_filedoctorskills());
break;
case LOADING:
break;
}
}
#Override
public int getItemCount() {
return consultancies.size();
}
#Override
public int getItemViewType(int position) {
return (position == consultancies.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
public void add(Consultancy mc) {
consultancies.add(mc);
notifyItemInserted(consultancies.size() - 1);
}
public void addAll(List<Consultancy> mcList) {
for (Consultancy mc : mcList) {
add(mc);
}
}
public void remove(Consultancy city) {
int position = consultancies.indexOf(city);
if (position > -1) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public Consultancy getItem(int position) {
return consultancies.get(position);
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Consultancy());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = consultancies.size() - 1;
Consultancy item = getItem(position);
if (item != null) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public interface ConsultancyAdapterListener {
void onCaseClicked(int position, String nid, String fieldArea, String title);
}
protected class ConsultancyVH extends RecyclerView.ViewHolder {
private TextView title, fieldArea;
private CircleImageView iconProfile;
private MaterialRippleLayout caseButtonRipple;
public ConsultancyVH(View itemView) {
super(itemView);
caseButtonRipple = itemView.findViewById(R.id.case_button_ripple);
this.title = itemView.findViewById(R.id.docName);
this.fieldArea = itemView.findViewById(R.id.fieldArea);
this.iconProfile = itemView.findViewById(R.id.icon_profile);
}
}
}
and in your activity:
private void setScrollListener() {
recyclerView.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
loadNextPage();
}
#Override
public boolean isLastPage() {
return isLastPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
}
and in my loadFirstPage i talk to a API and you need some your code:
private void loadFirstPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
dataList = response;
adapter.addAll(dataList);
recyclerView.setAdapter(adapter);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}
and loadNextPage:
private void loadNextPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
adapter.removeLoadingFooter();
isLoading = false;
swipeRefreshLayout.setRefreshing(false);
adapter.addAll(response);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}
I am currently trying to achieve an app to edit weekly timeframes.
Like mechanical timers for sockets, but in my case for every weekday.
The granularity is in the first place secondary (i guess it will be 15 or 30min).
My approach was a RecyclerView with GridLayoutManager and an ArrayAdapter with Items for every Cell.
To select more cells you can longpress a cell and drag over others. To achieve this I used the Listeners of the following Library DragSelectRecyclerView.
It works pretty well and you can select the items quite good but especially on the emulator or older phones its very slow and laggy. In the debug logcat you can see also that the Choreographer has to skip many frames at rendering the View and on selection of multiple cells.
Is there an better approach to achieve such an behavior. Or is there any big mistake in the code which is very slow and crappy?
EDIT:
after changing notifyItemChanged(pos); to notifyItemRangeChanged(pos, pos); its way less laggy but still not performing as well as it should.
I also removed everything that was responsible for autoscrolling (which was a feature of the library i mentioned above) to make the code simpler.
Here the source of my Fragment
public class TestFragment extends Fragment
{
#BindView(R.id.gridView_hours) GridView gridView_hours;
#BindView(R.id.weekdays_container) LinearLayout weekdays_container;
private String[] hours = new String[]{"00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"};
private String[] hoursShort = new String[]{"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"};
#Override
public void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_test, container, false);
ButterKnife.bind(this, view);
for (int i = 1; i <= 7; i++)
{
RecyclerView recyclerView = new RecyclerView(getActivity());
initAdapter(recyclerView);
GridLayoutManager glm = new GridLayoutManager(getActivity(), 48, GridLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(glm);
weekdays_container.addView(recyclerView);
}
gridView_hours.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.hour_view, hoursShort));
// GridLayoutManager glm = new GridLayoutManager(getActivity(), 48, GridLayoutManager.VERTICAL, false);
// recyclerView.setLayoutManager(glm);
return view;
}
private void initAdapter(RecyclerView recyclerView)
{
TestAutoDataAdapter adapter = new TestAutoDataAdapter(getActivity(), 48);
recyclerView.setAdapter(adapter);
DragSelectionProcessor dragSelectionProcessor = new DragSelectionProcessor(new DragSelectionProcessor.ISelectionHandler() {
#Override
public HashSet<Integer> getSelection() {
return adapter.getSelection();
}
#Override
public boolean isSelected(int index) {
return adapter.getSelection().contains(index);
}
#Override
public void updateSelection(int start, int end, boolean isSelected, boolean calledFromOnStart) {
adapter.selectRange(start, end, isSelected);
}
});
DragSelectTouchListener dragSelectTouchListener = new DragSelectTouchListener()
.withSelectListener(dragSelectionProcessor);
recyclerView.addOnItemTouchListener(dragSelectTouchListener);
adapter.setClickListener(new TestAutoDataAdapter.ItemClickListener()
{
#Override
public void onItemClick(View view, int position)
{
adapter.toggleSelection(position);
}
#Override
public boolean onItemLongClick(View view, int position)
{
dragSelectTouchListener.startDragSelection(position);
return true;
}
});
}
}
and the RecyclerView.Adapter
public class TestAutoDataAdapter extends RecyclerView.Adapter<TestAutoDataAdapter.ViewHolder>
{
private int dataSize;
private Context context;
private ItemClickListener clickListener;
private HashSet<Integer> selected;
public TestAutoDataAdapter(Context context, int size)
{
this.context = context;
dataSize = size;
selected = new HashSet<>();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(context).inflate(R.layout.test_cell, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position)
{
holder.tvText.setText("");
if (selected.contains(position))
holder.tvText.setBackgroundColor(Color.RED);
else
holder.tvText.setBackgroundColor(Color.WHITE);
}
#Override
public int getItemCount()
{
return dataSize;
}
// ----------------------
// Selection
// ----------------------
public void toggleSelection(int pos)
{
if (selected.contains(pos))
selected.remove(pos);
else
selected.add(pos);
notifyItemChanged(pos);
}
public void select(int pos, boolean selected)
{
if (selected)
this.selected.add(pos);
else
this.selected.remove(pos);
notifyItemRangeChanged(pos, pos);
}
public void selectRange(int start, int end, boolean selected)
{
for (int i = start; i <= end; i++)
{
if (selected)
this.selected.add(i);
else
this.selected.remove(i);
}
notifyItemRangeChanged(start, end - start + 1);
}
public void deselectAll()
{
// this is not beautiful...
selected.clear();
notifyDataSetChanged();
}
public void selectAll()
{
for (int i = 0; i < dataSize; i++)
selected.add(i);
notifyDataSetChanged();
}
public int getCountSelected()
{
return selected.size();
}
public HashSet<Integer> getSelection()
{
return selected;
}
// ----------------------
// Click Listener
// ----------------------
public void setClickListener(ItemClickListener itemClickListener)
{
clickListener = itemClickListener;
}
public interface ItemClickListener
{
void onItemClick(View view, int position);
boolean onItemLongClick(View view, int position);
}
// ----------------------
// ViewHolder
// ----------------------
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener
{
public TextView tvText;
public ViewHolder(View itemView)
{
super(itemView);
tvText = itemView.findViewById(R.id.tvText);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
#Override
public void onClick(View view)
{
if (clickListener != null)
clickListener.onItemClick(view, getAdapterPosition());
}
#Override
public boolean onLongClick(View view)
{
if (clickListener != null)
return clickListener.onItemLongClick(view, getAdapterPosition());
return false;
}
}
}
the SelectTouchListener
public class DragSelectTouchListener implements RecyclerView.OnItemTouchListener
{
private static final String TAG = "DSTL";
private boolean mIsActive;
private int mStart, mEnd;
private int mLastStart, mLastEnd;
private OnDragSelectListener mSelectListener;
public DragSelectTouchListener()
{
reset();
}
/**
* sets the listener
* <p>
*
* #param selectListener the listener that will be notified when items are (un)selected
*/
public DragSelectTouchListener withSelectListener(OnDragSelectListener selectListener)
{
this.mSelectListener = selectListener;
return this;
}
// -----------------------
// Main functions
// -----------------------
/**
* start the drag selection
* <p>
*
* #param position the index of the first selected item
*/
public void startDragSelection(int position)
{
setIsActive(true);
mStart = position;
mEnd = position;
mLastStart = position;
mLastEnd = position;
if (mSelectListener != null && mSelectListener instanceof OnAdvancedDragSelectListener)
((OnAdvancedDragSelectListener)mSelectListener).onSelectionStarted(position);
}
// -----------------------
// Functions
// -----------------------
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
{
if (!mIsActive || rv.getAdapter().getItemCount() == 0)
return false;
int action = e.getAction();
switch (action)
{
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
reset();
break;
}
return true;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e)
{
if (!mIsActive)
return;
int action = e.getAction();
switch (action)
{
case MotionEvent.ACTION_MOVE:
updateSelectedRange(rv, e);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
reset();
break;
}
}
private void updateSelectedRange(RecyclerView rv, MotionEvent e)
{
updateSelectedRange(rv, e.getX(), e.getY());
}
private void updateSelectedRange(RecyclerView rv, float x, float y)
{
View child = rv.findChildViewUnder(x, y);
if (child != null)
{
int position = rv.getChildAdapterPosition(child);
if (position != RecyclerView.NO_POSITION && mEnd != position)
{
mEnd = position;
notifySelectRangeChange();
}
}
}
private void notifySelectRangeChange()
{
if (mSelectListener == null)
return;
if (mStart == RecyclerView.NO_POSITION || mEnd == RecyclerView.NO_POSITION)
return;
int newStart, newEnd;
newStart = Math.min(mStart, mEnd);
newEnd = Math.max(mStart, mEnd);
if (mLastStart == RecyclerView.NO_POSITION || mLastEnd == RecyclerView.NO_POSITION)
{
if (newEnd - newStart == 1)
mSelectListener.onSelectChange(newStart, newStart, true);
else
mSelectListener.onSelectChange(newStart, newEnd, true);
}
else
{
if (newStart > mLastStart)
mSelectListener.onSelectChange(mLastStart, newStart - 1, false);
else if (newStart < mLastStart)
mSelectListener.onSelectChange(newStart, mLastStart - 1, true);
if (newEnd > mLastEnd)
mSelectListener.onSelectChange(mLastEnd + 1, newEnd, true);
else if (newEnd < mLastEnd)
mSelectListener.onSelectChange(newEnd + 1, mLastEnd, false);
}
mLastStart = newStart;
mLastEnd = newEnd;
}
private void reset()
{
setIsActive(false);
if (mSelectListener != null && mSelectListener instanceof OnAdvancedDragSelectListener)
((OnAdvancedDragSelectListener)mSelectListener).onSelectionFinished(mEnd);
mStart = RecyclerView.NO_POSITION;
mEnd = RecyclerView.NO_POSITION;
mLastStart = RecyclerView.NO_POSITION;
mLastEnd = RecyclerView.NO_POSITION;
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
{
// ignore
}
public void setIsActive(boolean isActive)
{
this.mIsActive = isActive;
}
// -----------------------
// Interfaces and simple default implementations
// -----------------------
public interface OnAdvancedDragSelectListener extends OnDragSelectListener
{
/**
* #param start the item on which the drag selection was started at
*/
void onSelectionStarted(int start);
/**
* #param end the item on which the drag selection was finished at
*/
void onSelectionFinished(int end);
}
public interface OnDragSelectListener
{
/**
* #param start the newly (un)selected range start
* #param end the newly (un)selected range end
* #param isSelected true, it range got selected, false if not
*/
void onSelectChange(int start, int end, boolean isSelected);
}
}
and the Implementation of the needed Interface
public class DragSelectionProcessor implements DragSelectTouchListener.OnAdvancedDragSelectListener {
private ISelectionHandler mSelectionHandler;
private HashSet<Integer> mOriginalSelection;
private boolean mFirstWasSelected;
private boolean mCheckSelectionState = false;
public DragSelectionProcessor(ISelectionHandler selectionHandler)
{
mSelectionHandler = selectionHandler;
}
#Override
public void onSelectionStarted(int start)
{
mOriginalSelection = new HashSet<>();
Set<Integer> selected = mSelectionHandler.getSelection();
if (selected != null)
mOriginalSelection.addAll(selected);
mFirstWasSelected = mOriginalSelection.contains(start);
mSelectionHandler.updateSelection(start, start, !mFirstWasSelected, true);
}
#Override
public void onSelectionFinished(int end)
{
mOriginalSelection = null;
}
#Override
public void onSelectChange(int start, int end, boolean isSelected)
{
for (int i = start; i <= end; i++)
checkedUpdateSelection(i, i, isSelected ? !mFirstWasSelected : mOriginalSelection.contains(i));
}
private void checkedUpdateSelection(int start, int end, boolean newSelectionState)
{
if (mCheckSelectionState)
{
for (int i = start; i <= end; i++)
{
if (mSelectionHandler.isSelected(i) != newSelectionState)
mSelectionHandler.updateSelection(i, i, newSelectionState, false);
}
}
else
mSelectionHandler.updateSelection(start, end, newSelectionState, false);
}
public interface ISelectionHandler
{
Set<Integer> getSelection();
boolean isSelected(int index);
void updateSelection(int start, int end, boolean isSelected, boolean calledFromOnStart);
}
}
I wouldn't use notifyItemRangeChanged(pos, pos);, notifyDataSetChanged() etc. on each selection process.
Why won't you like this;
1.Took your recyclerview references in its adapter like this:
private RecyclerView mRecyclerView;
public TestAutoDataAdapter(Context context, int size, RecyclerView pRecyclerView){
this.mRecyclerView = pRecyclerView;
..
2.Set background color of the selected viewholder like this:
public void select(int pos, boolean selected){
// Get selected view holder from recyclerview
ViewHolder holder = recyclerview..findViewHolderForAdapterPosition(pos);
if (selected)
this.selected.add(pos);
else
this.selected.remove(pos);
//notifyItemRangeChanged(pos, pos);
holder.tvText.setBackgroundColor(selected ? Color.RED : Color.WHITE);
}
Change your whole selection process like this way and let me know the result.
Hello friends.
I have navigation drawer with a menu item when clicking on each menu nav item, one fragment loaded, inside this fragments, I have recycled view items. Now I want use searchView filter for this items. I created a searchView icon in the toolbar, and write codes for SearchView inside Adapter(setFilter) and etc, but nothing happen when writing names item.
this is my fragment.
AfricaFragment`
public class AfricaFragment extends Fragment implements SearchView.OnQueryTextListener {
public static final String AFRICA_FRAGMENT = "africa_fragment";
ArrayList<AsiaCountry> asiaCountries = new ArrayList<>();
ContentAdapter contentAdapter;
RecyclerView recyclerView;
//FastScroller fastScroller;
private boolean isListView;
private Menu menu;
private StaggeredGridLayoutManager staggeredGridLayoutManager;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
//The parameter value of false indicates that container is to be used
// only for the layout parameters.
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.africa_fragment, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
// fastScroller = (FastScroller) view.findViewById(R.id.fast_scrollview);
isListView = true;
loadDataBase();
return view;
}
public void toggle() {
MenuItem item = menu.findItem(R.id.grid);
if (isListView) {
staggeredGridLayoutManager.setSpanCount(2);
item.setIcon(R.drawable.ic_vertical);
item.setTitle("Show as list");
isListView = false;
} else {
staggeredGridLayoutManager.setSpanCount(1);
item.setIcon(R.drawable.ic_grid);
item.setTitle("Show grid");
isListView = true;
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.grid_item, menu);
this.menu = menu;
Log.d("Menu created", "grid");
final MenuItem menuItem = menu.findItem(R.id.search);
SearchView searchView = (SearchView)
MenuItemCompat.getActionView(menuItem);
searchView.setOnQueryTextListener(this);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.grid) {
Toast.makeText(getActivity(), "Grid item touched", Toast.LENGTH_SHORT).show();
toggle();
return true;
}
if (id == R.id.settings) {
Toast.makeText(getActivity(), "Setting clicked", Toast.LENGTH_SHORT).show();
return true;
}
if (id == R.id.search) {
return true;
}
return onOptionsItemSelected(item);
}
public void loadDataBase() {
WorldCountry worldCountry = new WorldCountry(getActivity());
try {
worldCountry.createDatabase();
worldCountry.openDataBase();
Log.d("TAG", "Database open");
} catch (SQLiteException o) {
o.printStackTrace();
Log.d("Tag", o.getMessage());
}
try {
Cursor cursor = worldCountry.QueryData("SELECT name, viewImage FROM country WHERE continent ='آفریقا'");
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
AsiaCountry asiaCountry = new AsiaCountry();
asiaCountry.setName(cursor.getString(0));
asiaCountry.setImageResourceID(cursor.getString(1));
asiaCountries.add(asiaCountry);
} while (cursor.moveToNext());
worldCountry.close();
}
}
} catch (SQLiteException o) {
o.printStackTrace();
Log.d("TAG", o.getMessage());
}
staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
contentAdapter = new ContentAdapter(getActivity(), asiaCountries);
contentAdapter.notifyDataSetChanged();
contentAdapter.setListener(new ContentAdapter.Listener() {
#Override
public void onClick(int position) {
}
});
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
recyclerView.setAdapter(contentAdapter);
}
#Override
public boolean onQueryTextSubmit(String query) {
if (contentAdapter != null) {
query = String.valueOf(query.equals(""));
}
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
newText = newText.toLowerCase();
ArrayList<AsiaCountry> asiaCountryArrayList = new ArrayList<>();
for (AsiaCountry asiaCountry : asiaCountries) {
String name = asiaCountry.getName().toLowerCase();
if (name.contains(newText))
asiaCountryArrayList.add(asiaCountry);
}
contentAdapter.setFilter(asiaCountryArrayList);
return true;
}
}
this my adapter.
ContentAdapter
public class ContentAdapter extends RecyclerView.Adapter<ContentAdapter.ViewHolder> {
public Context context;
private List<AsiaCountry> item = Collections.emptyList();
private int mLastPosition = -1;
private Listener listener;
public ContentAdapter(Context context, List<AsiaCountry> item) {
this.context = context;
this.item = item;
}
public void setListener(Listener listener) {
this.listener = listener;
}
#Override
public ContentAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
//Defain the view for take layout
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
Log.d("Adapter", "Loaded");
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
AsiaCountry asia = item.get(position);
holder.titleNameCountry.setText(asia.getName());
Log.d("TAG", asia.getName());
// String imageFile = asia.getImageResourceID();
holder.titleImageCountry.setImageBitmap(loadBitmapFromAssets(context, asia.getImageResourceID()));
/* Glide.with(context)
.load(loadBitmapFromAssets(context, asia.getImageResourceID()))
.asBitmap()
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
holder.titleImageCountry.setImageBitmap(resource);
}
});*/
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
listener.onClick(position);
Toast.makeText(context, holder.titleNameCountry.getText().toString() + "is clicked",
Toast.LENGTH_LONG).show();
String nameCountry = holder.titleNameCountry.getText().toString();
Intent intent = new Intent(context, DetialsCountry.class);
intent.putExtra("name", nameCountry);
Log.d("CONT", nameCountry);
context.startActivity(intent);
}
}
});
// When items loading, must be show in animation, for UX better.
if (position > mLastPosition) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(holder.itemView, "translationY",
holder.itemView.getMeasuredHeight() * 2, 0));
ObjectAnimator.ofFloat(holder.itemView, "alpha", 0, 1);
ObjectAnimator.ofFloat(holder.itemView, "scaleX", 0.5f, 1);
ObjectAnimator.ofFloat(holder.itemView, "scaleY", 0.5f, 1);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.start();
mLastPosition = position;
} else {
ViewHolder.clear(holder.itemView);
}
/* Bitmap photo = BitmapFactory.decodeResource(context.getResources(), asia.getImageResourceID(context));
Palette.from(photo).generate(new Palette.PaletteAsyncListener() {
#Override
public void onGenerated(Palette palette) {
int bgColor = palette.getDarkMutedColor(ContextCompat.getColor(context, android.R.color.black));
holder.titleNameCountry.setBackgroundColor(bgColor);
}
});*/
/* Bitmap photo = BitmapFactory.decodeResource(context.getResources(), country.getTitleCountry(context));
Palette.generateAsync(photo, new Palette.PaletteAsyncListener() {
public void onGenerated(Palette palette) {
int mutedLight = palette.getMutedColor(context.getResources().getColor(android.R.color.black));
holder.titleNameCountry.setBackgroundColor(mutedLight);
}
});*/
}
#Override
public int getItemCount() {
return item.size();
}
//This method take getAssets from assets folder and compare with cursor.getString(1), and show in recyclerview.
#Nullable
private Bitmap loadBitmapFromAssets(Context context, String path) {
InputStream stream = null;
try {
stream = context.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
} catch (Exception ignored) {
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (Exception ignored) {
}
}
return null;
}
public void setFilter(ArrayList<AsiaCountry> arrayList) {
arrayList = new ArrayList<>();
item.addAll(arrayList);
notifyDataSetChanged();
}
public static interface Listener {
public void onClick(int position);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView titleImageCountry;
private TextView titleNameCountry;
// Defian the viewHolder
private ViewHolder(View itemView) {
super(itemView);
titleImageCountry = (ImageView) itemView.findViewById(R.id.imageHolder);
titleNameCountry = (TextView) itemView.findViewById(R.id.titleCountry);
}
// public static class ViewHolder {
private static void clear(View itemView) {
ViewCompat.setAlpha(itemView, 1);
ViewCompat.setTranslationY(itemView, 0);
itemView.setPivotY(itemView.getMeasuredHeight() / 2);
ViewCompat.setScaleX(itemView, 1);
ViewCompat.setScaleY(itemView, 1);
ViewCompat.animate(itemView).setInterpolator(null);
Log.d("Animationg", "Loaded");
}
}
}
Please help me :)
In your setFilter in Adapter method, you are again creating object for the parameter you sent, So remove that and made the global item as empty
public void setFilter(ArrayList<AsiaCountry> arrayList) {
//arrayList = new ArrayList<>(); // remove this line
item.clear(); // add this so that it will clear old data
item.addAll(arrayList);
notifyDataSetChanged();
}
To reset the text we should handle in 2 place one is when user press ok, it will call onQueryTextSubmit, second way is user click back arrow at the top, So we should handle both,
#Override
public boolean onQueryTextSubmit(String query) {
// Log.d(TAG, "onQueryTextSubmit() called with: query = [" + query + "]");
if (mAdapter != null)
// reset your complete list here
return true;
}
Second place is,
MenuItemCompat.setOnActionExpandListener(mSearchItem,
new MenuItemCompat.OnActionExpandListener() {
#Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
// Log.d(TAG, "onMenuItemActionExpand() called with: menuItem = [" + menuItem + "]");
return true;
}
#Override
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
// Log.d(TAG, "onMenuItemActionCollapse() called with: menuItem = [" + menuItem + "]");
// update your list with complete list
return true;
}
});
I'd like to implement streaming video list app. I used a RecyclerView to display my list item. Item type includes 4 type: Article, Status, Photo and Video. We should only focus on Video type. Here is my code for RecyclerView's adapter:
public class FollowedPostAdapter extends RecyclerView.Adapter implements OnFollowTagCallback, OnLikeCallback {
private Context context;
private List<PostItem> newsFeedList;
public RecyclerView recyclerView;
public LinearLayoutManager linearLayoutManager;
private HashMap<Integer, DemoPlayer> playerList;
private int visibleThreshold = 5;
// private int previousTotal = 0;
private int visibleItemCount, firstVisibleItem, totalItemCount;
private boolean loading;
private OnRecyclerViewLoadMoreListener loadMoreListener;
private final String text_comment;
private final String mReadMoreHtml;
private long userId;
public FollowedPostAdapter(Context context, RecyclerView recyclerView, List<PostItem> newsFeedList) {
this.context = context;
playerList = new HashMap<>();
this.newsFeedList = newsFeedList;
this.recyclerView = recyclerView;
this.linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if (viewType == Constants.VIEWTYPE_ARTICLE) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_article_item, parent, false);
viewHolder = new ArticleViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_STATUS) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_status_item, parent, false);
viewHolder = new StatusViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_PHOTO) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_photo_item, parent, false);
viewHolder = new PhotoViewHolder(view);
} else if (viewType == Constants.VIEWTYPE_VIDEO) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_followed_video_item, parent, false);
viewHolder = new VideoViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.progressbar_item, parent, false);
viewHolder = new ProgressBarViewHolder(view);
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ArticleViewHolder) {
// code
} else if (holder instanceof StatusViewHolder) {
// code
} else if (holder instanceof PhotoViewHolder) {
// code
} else if (holder instanceof VideoViewHolder) {
PostItem item = newsFeedList.get(position);
VideoViewHolder mHolder = (VideoViewHolder) holder;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mHolder.videoView.getLayoutParams();
lp.height = (int) (ScreenHelper.getScreenWidth((Activity) context) * Constants.POST_IMAGE_RATIO);
mHolder.videoView.setLayoutParams(lp);
mHolder.setup(item);
Picasso.with(context).load(item.imageCover).error(R.drawable.ic_user_avatar).placeholder(R.drawable.ic_user_avatar).into(mHolder.iv_avatar);
if (item.tags != null && item.tags.size() > 0) {
// get first tag as main tag
generateTagViews(mHolder.tag_flow_layout, item.tags.subList(0, 1), position);
mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
mHolder.tag_flow_layout.setVisibility(View.VISIBLE);
// mHolder.indicator.setVisibility(View.VISIBLE);
} else {
mHolder.tag_flow_layout.setVisibility(View.GONE);
// mHolder.indicator.setVisibility(View.GONE);
}
if (item.time_created != null) {
mHolder.tv_time.setText(item.time_created);
//mHolder.indicator.setVisibility(View.VISIBLE);
mHolder.tv_time.setVisibility(View.VISIBLE);
} else {
//mHolder.indicator.setVisibility(View.GONE);
mHolder.tv_time.setVisibility(View.GONE);
}
if (item.description_short.isEmpty())
mHolder.tv_description.setVisibility(View.GONE);
else {
mHolder.tv_description.setText(item.description_short);
mHolder.tv_description.setVisibility(View.VISIBLE);
}
mHolder.btn_comment.setText(String.valueOf(item.count_comment));
mHolder.btn_like.setText(String.valueOf(item.count_like));
mHolder.btn_unlike.setText(String.valueOf(item.count_unlike));
mHolder.btn_share.setText(String.valueOf(item.count_share));
if (item.tags.size() != 0) {
int tagId = item.tags.get(0).tag_id;
setFollowButtonActive(mHolder.btn_follow, TagHelper.isTagFollowed(tagId));
} else
setFollowButtonActive(mHolder.btn_follow, false);
}
}
#Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
if (holder instanceof VideoViewHolder) {
DemoPlayer player = playerList.get(holder.getAdapterPosition());
if (player != null) {
player.release();
playerList.remove(holder.getAdapterPosition());
}
}
}
public void pauseAllPlayers() {
for (int i = 0; i <= newsFeedList.size(); i++) {
DemoPlayer player = playerList.get(i);
if (player != null) {
if (player.getPlayerControl().isPlaying())
player.getPlayerControl().pause();
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForLayoutPosition(i);
if (holder != null && holder instanceof VideoViewHolder) {
((VideoViewHolder) holder).btn_play.setVisibility(View.VISIBLE);
}
}
}
}
public void refreshData() {
notifyDataSetChanged();
}
public class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
//
}
public class StatusViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// code
}
public class PhotoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// code
}
public class VideoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, SurfaceHolder.Callback, AudioCapabilitiesReceiver.Listener, DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener {
#Bind(R.id.iv_avatar)
ImageView iv_avatar;
#Bind(R.id.tag_flow_layout)
FlowLayout tag_flow_layout;
#Bind(R.id.tv_time)
TextView tv_time;
#Bind(R.id.btn_follow)
FancyButton btn_follow;
#Bind(R.id.btn_comment)
FancyButton btn_comment;
#Bind(R.id.btn_like)
FancyButton btn_like;
#Bind(R.id.btn_unlike)
FancyButton btn_unlike;
#Bind(R.id.btn_share)
FancyButton btn_share;
#Bind(R.id.root)
FrameLayout videoView;
#Bind(R.id.btn_play)
ImageView btn_play;
#Bind(R.id.tv_description)
TextView tv_description;
// player's variable
private EventLogger eventLogger;
// private VideoControllerView mediaController;
private View shutterView;
private AspectRatioFrameLayout videoFrame;
private SurfaceView surfaceView;
private SubtitleLayout subtitleLayout;
private DemoPlayer player;
private boolean playerNeedsPrepare;
private long playerPosition = 0;
private Uri contentUri;
private int contentType;
private String contentId;
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_OTHER = 3;
private static final String EXT_DASH = ".mpd";
private static final String EXT_SS = ".ism";
private static final String EXT_HLS = ".m3u8";
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
public VideoViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
iv_avatar.setOnClickListener(this);
btn_follow.setOnClickListener(this);
btn_comment.setOnClickListener(this);
btn_like.setOnClickListener(this);
btn_unlike.setOnClickListener(this);
btn_share.setOnClickListener(this);
// player's setup
View root = view.findViewById(R.id.root);
root.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
doPlayResume();
}
});
// root.setOnTouchListener(new View.OnTouchListener() {
// #Override
// public boolean onTouch(View view, MotionEvent motionEvent) {
// if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
// toggleControlsVisibility();
// } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
// view.performClick();
// }
// return true;
// }
// });
// root.setOnKeyListener(new View.OnKeyListener() {
// #Override
// public boolean onKey(View v, int keyCode, KeyEvent event) {
// if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
// || keyCode == KeyEvent.KEYCODE_MENU) {
// return false;
// }
// return mediaController.dispatchKeyEvent(event);
// }
// });
shutterView = view.findViewById(R.id.shutter);
videoFrame = (AspectRatioFrameLayout) view.findViewById(R.id.video_frame);
surfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
subtitleLayout = (SubtitleLayout) view.findViewById(R.id.subtitles);
// mediaController = new VideoControllerView(context);
// mediaController.setAnchorView((ViewGroup) root);
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(context, this);
audioCapabilitiesReceiver.register();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_avatar:
viewTagDetail(getAdapterPosition());
break;
case R.id.btn_follow:
followTag((FancyButton) v, getAdapterPosition());
break;
case R.id.btn_comment:
commentPost(getAdapterPosition());
break;
case R.id.btn_like:
likePost(getAdapterPosition(), btn_like, btn_unlike);
break;
case R.id.btn_unlike:
unlikePost(getAdapterPosition(), btn_like, btn_unlike);
break;
case R.id.btn_share:
sharePost(getAdapterPosition());
break;
case R.id.btn_play:
doPlayResume();
break;
}
}
public void setup(PostItem item) {
releasePlayer();
player = playerList.get(getAdapterPosition());
contentUri = Uri.parse(item.link);
contentType = TYPE_OTHER;
contentId = String.valueOf(item.post_id);
configureSubtitleView();
if (player == null) {
preparePlayer(false);
} else {
player.setBackgrounded(false);
}
}
// public void saveCurrentPosition() {
// if (player != null)
// videoItemList.get(getAdapterPosition()).position = player.getCurrentPosition();
// }
public void doPlayResume() {
if (player == null) {
return;
}
if (player.getPlayerControl().isPlaying()) {
player.getPlayerControl().pause();
} else {
player.getPlayerControl().start();
}
showControls();
}
#Override
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
if (player == null) {
return;
}
boolean backgrounded = player.getBackgrounded();
boolean playWhenReady = player.getPlayWhenReady();
releasePlayer();
preparePlayer(playWhenReady);
player.setBackgrounded(backgrounded);
}
private DemoPlayer.RendererBuilder getRendererBuilder() {
String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
switch (contentType) {
case TYPE_SS:
return new SmoothStreamingRendererBuilder(context, userAgent, contentUri.toString(),
new SmoothStreamingTestMediaDrmCallback());
case TYPE_DASH:
return new DashRendererBuilder(context, userAgent, contentUri.toString(),
new WidevineTestMediaDrmCallback(contentId));
case TYPE_HLS:
return new HlsRendererBuilder(context, userAgent, contentUri.toString());
case TYPE_OTHER:
return new ExtractorRendererBuilder(context, userAgent, contentUri);
default:
throw new IllegalStateException("Unsupported type: " + contentType);
}
}
private void preparePlayer(boolean playWhenReady) {
if (player == null) {
player = new DemoPlayer(getRendererBuilder());
playerList.put(getAdapterPosition(), player);
player.addListener(this);
player.setCaptionListener(this);
player.setMetadataListener(this);
player.seekTo(playerPosition);
playerNeedsPrepare = true;
// mediaController.setMediaPlayer(player.getPlayerControl());
// mediaController.setEnabled(true);
eventLogger = new EventLogger();
eventLogger.startSession();
player.addListener(eventLogger);
player.setInfoListener(eventLogger);
player.setInternalErrorListener(eventLogger);
}
if (playerNeedsPrepare) {
player.prepare();
playerNeedsPrepare = false;
}
player.setSurface(surfaceView.getHolder().getSurface());
player.setPlayWhenReady(playWhenReady);
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
eventLogger.endSession();
eventLogger = null;
btn_play.setVisibility(View.VISIBLE);
}
}
#Override
public void onStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
showControls();
}
}
#Override
public void onError(Exception e) {
if (e instanceof UnsupportedDrmException) {
// Special case DRM failures.
UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
int stringId = Util.SDK_INT < 18 ? R.string.drm_error_not_supported
: unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.drm_error_unsupported_scheme : R.string.drm_error_unknown;
Toast.makeText(context, stringId, Toast.LENGTH_LONG).show();
}
playerNeedsPrepare = true;
showControls();
}
#Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
shutterView.setVisibility(View.GONE);
videoFrame.setAspectRatio(
height == 0 ? 1 : (width * pixelWidthHeightRatio) / height);
}
private boolean haveTracks(int type) {
return player != null && player.getTrackCount(type) > 0;
}
// private void toggleControlsVisibility() {
// if (mediaController.isShowing()) {
// mediaController.hide();
// } else {
// showControls();
// }
// }
private void showControls() {
// mediaController.show(5000);
if (player.getPlayerControl().isPlaying())
btn_play.setVisibility(View.GONE);
else
btn_play.setVisibility(View.VISIBLE);
}
#Override
public void onCues(List<Cue> cues) {
subtitleLayout.setCues(cues);
}
#Override
public void onId3Metadata(Map<String, Object> metadata) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (player != null) {
player.setSurface(holder.getSurface());
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (player != null) {
holder.lockCanvas();
player.blockingClearSurface();
}
}
private void configureSubtitleView() {
CaptionStyleCompat style;
float fontScale;
if (Util.SDK_INT >= 19) {
style = getUserCaptionStyleV19();
fontScale = getUserCaptionFontScaleV19();
} else {
style = CaptionStyleCompat.DEFAULT;
fontScale = 1.0f;
}
subtitleLayout.setStyle(style);
subtitleLayout.setFractionalTextSize(SubtitleLayout.DEFAULT_TEXT_SIZE_FRACTION * fontScale);
}
#TargetApi(19)
private float getUserCaptionFontScaleV19() {
CaptioningManager captioningManager =
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
return captioningManager.getFontScale();
}
#TargetApi(19)
private CaptionStyleCompat getUserCaptionStyleV19() {
CaptioningManager captioningManager =
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
private int inferContentType(Uri uri, String fileExtension) {
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
: uri.getLastPathSegment();
if (lastPathSegment == null) {
return TYPE_OTHER;
} else if (lastPathSegment.endsWith(EXT_DASH)) {
return TYPE_DASH;
} else if (lastPathSegment.endsWith(EXT_SS)) {
return TYPE_SS;
} else if (lastPathSegment.endsWith(EXT_HLS)) {
return TYPE_HLS;
} else {
return TYPE_OTHER;
}
}
}
public class ProgressBarViewHolder extends RecyclerView.ViewHolder {
public ProgressBarViewHolder(View view) {
super(view);
}
}
public void unregisterEventBus() {
EventBus.getDefault().unregister(this);
}
}
When item type is video, I create an ExoPlayer instance to play this video and add this instance to a HashMap for later use (with key value is the item's position). If a video item is being recycled, I release the player and remove it from HashMap.
Everything looks good but a problem. I suppose the video item is at 0 position (now we can see the video preview in this item). I scroll down the RecyclerView just enough to hide the item 0. At this moment, the VideoViewHolder of item 0 is not recycled yet. Then, I scroll up to reveal item 0. The view is now blank, not show the video preview. I click item 0 to play the video, the player only plays audio (sound), not show video. After playing a few seconds, the video is now visible.
I debugged and found that after I scrolled down to hide video item, the SurfaceView is destroyed. When scroll up to reveal video item, the SurfaceView is created. I think this is the reason why VideoViewHolder shows blank view instead of the video preview.
Here is my question: How to display video preview after scroll back to video item?
Here is note from ExoPlayer documentation
SurfaceView rendering wasn’t properly synchronized with view animations until Android N. On earlier releases this could result in unwanted effects when a SurfaceView was placed into scrolling container, or when it was subjected to animation. Such effects included the SurfaceView’s contents appearing to lag slightly behind where it should be displayed, and the view turning black when subjected to animation.
To achieve smooth animation or scrolling of video prior to Android N, it’s therefore necessary to use TextureView rather than SurfaceView. If smooth animation or scrolling is not required then SurfaceView should be preferred
To enable TextureView surface_type you have to set it in your xml file like shown below
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="#+id/playerView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:surface_type="texture_view"/>
am building an open source project, here is its link
http://slidese.github.io/slidese/2013/11/25/update_listview_item.html
I've imported all library projects which it is using into eclipse. All are fine except PullToRefresh library. it is giving me the error "PullToRefreshAttacher.OnRefreshListener cannot be resolved to a type" where fragment implements it
2nd error is at
mPullToRefreshAttacher.addRefreshableView(mListview, this);
it says "The method addRefreshableView(View, ViewDelegate) in the type PullToRefreshAttacher is not applicable for the arguments (ListView, ContentFragment)"
3rd error is at
#Override
public void onRefreshStarted(View view) {
Intent intent = new Intent(getActivity(), DownloaderService.class);
intent.putExtra(DownloaderService.EXTRA_USER_INITIATED, true);
getActivity().startService(intent);
}
It is asking me to remove override annotation. here is complete code of fragment.
public class ContentFragment extends Fragment implements PullToRefreshAttacher.OnRefreshListener {
private final String TAG = "ContentFragment";
public static final String CONTENT_MODE = "content_mode";
public static final int MODE_ADFREE = 0;
public static final int MODE_PREMIUM = 1;
private StartActivity mListener;
private PullToRefreshAttacher mPullToRefreshAttacher;
private UpdaterAsyncTask mUpdater;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
ListView mListview;
Button mPlayButton;
ContentAdapter mAdapter;
int mMode;
Map<String, UpdateHolder> mUpdates = new HashMap<String, UpdateHolder>();
public ContentFragment() {
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (StartActivity) activity;
Log.d(TAG, "Attached podcast list fragment");
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must be the StartActivity");
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, null);
mListview = (ListView) view.findViewById(android.R.id.list);
mListview.setEmptyView(view.findViewById(R.id.empty_list_view));
mListview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListview.setMultiChoiceModeListener(new MultiChoiceModeListener() {
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.contextual_menu_content, menu);
return true;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (item.getItemId() == R.id.action_delete) {
SparseBooleanArray checked = mListview.getCheckedItemPositions();
Content[] params = new Content[checked.size()];
int index = 0;
int first = mListview.getFirstVisiblePosition();
int last = mListview.getLastVisiblePosition();
for (int i = 0; i < mListview.getCount(); i++) {
if (checked.get(i)) {
params[index++] = (Content)mListview.getItemAtPosition(i);
if (i >= first && i <= last) {
View view = mListview.getChildAt(i-first);
Animation animation = AnimationUtils.loadAnimation(getActivity(), android.R.anim.slide_out_right);
animation.setDuration(200);
//animation.setFillAfter(true);
animation.setStartOffset(100 * (index) );
view.startAnimation(animation);
}
}
}
new AsyncTask<Content, Void, Void>() {
#Override
protected Void doInBackground(Content... params) {
for (Content content : params) {
File file = Utils.getFilepath(content.getFilename());
file.delete();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mAdapter.notifyDataSetChanged();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
mode.finish();
return true;
}
return false;
}
#Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
}
});
mListview.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
Content content = mAdapter.getItem(position);
mListener.showContentDetails(content);
}
});
mListview.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mScrollState = scrollState;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
mPullToRefreshAttacher = ((StartActivity) getActivity()).getPullToRefreshAttacher();
mPullToRefreshAttacher.addRefreshableView(mListview, this);
mMode = getArguments().getInt(CONTENT_MODE);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onPause() {
super.onPause();
if (mUpdater != null)
mUpdater.stop();
}
#Override
public void onResume() {
super.onResume();
updateAdapter();
mUpdater = new UpdaterAsyncTask();
mUpdater.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void)null);
}
public void refresh() {
updateAdapter();
}
public void replaceCurrentlyPlayingContent() {
GlobalContext.INSTANCE.replaceCurrentlyPLayingContent(mAdapter.getObjects(), mListener.getCurrentTrack());
}
private void updateAdapter() {
Log.d(TAG, "updateAdapter");
//new FetchContentAsyncTask(mMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
final String mp3 = mListener.getSavedStateMp3();
final boolean isPlaying = mListener.getSavedStateIsPlaying();
final boolean isPaused = mListener.getSavedStateIsPaused();
List<Content> listOfContent = GlobalContext.INSTANCE.getCachedContent(mMode, mp3, isPlaying, isPaused);
mAdapter = new ContentAdapter(getActivity(), R.layout.list_item_card, listOfContent);
mListview.setAdapter(mAdapter);
}
#Override
public void onRefreshStarted(View view) {
Intent intent = new Intent(getActivity(), DownloaderService.class);
intent.putExtra(DownloaderService.EXTRA_USER_INITIATED, true);
getActivity().startService(intent);
}
private class UpdaterAsyncTask extends AsyncTask<Void, Void, Void> {
boolean isRunning = true;
public void stop() {
isRunning = false;
}
#Override
protected Void doInBackground(Void... params) {
while (isRunning) {
/*
Map<String, UpdateHolder> map = gatherMetadata();
publishProgress(map);
*/
updateCurrentAdapterContent();
publishProgress();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... params) {
super.onProgressUpdate();
if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
// http://stackoverflow.com/questions/2123083/android-listview-refresh-single-row
int start = mListview.getFirstVisiblePosition();
for(int i = start, j = mListview.getLastVisiblePosition(); i<=j; i++) {
View view = mListview.getChildAt(i-start);
if (((Content)mListview.getItemAtPosition(i)).dirty) {
Log.v(TAG, "Content is dirty");
mListview.getAdapter().getView(i, view, mListview);
}
}
}
}
}
private void updateCurrentAdapterContent() {
List<Content> listOfContent = mAdapter.getObjects();
Map<String, UpdateHolder> map = new HashMap<String, UpdateHolder>();
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING);
try {
Cursor cursor = ContentDownloadManager.INSTANCE.query(q);
while (cursor.moveToNext()) {
//long id = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI));
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
int downloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
int total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
float progress = (float)downloaded/(float)total;
UpdateHolder holder = new UpdateHolder();
holder.progress = progress;
holder.status = status;
map.put(uri, holder);
}
cursor.close();
final Content currentContent = mListener.getCurrentTrack();
final boolean isPlaying = mListener.isPlaying();
final boolean isPaused = mListener.isPaused();
for (Content content : listOfContent) {
// First update any download progress we might have for this specific content item
UpdateHolder holder = map.get(content.mp3);
if (holder != null) {
if (content.downloadProgress != holder.progress) {
content.downloadProgress = holder.progress;
content.dirty = true;
}
if (content.downloadStatus != holder.status) {
content.downloadStatus = holder.status;
content.dirty = true;
}
}
else {
if (content.downloadProgress != 0f) {
content.downloadProgress = 0f;
content.dirty = true;
}
if (content.downloadStatus != -1) {
content.downloadStatus = -1;
content.dirty = true;
}
}
// Update with elapsed (to be done)
// File exists?
File file = Utils.getFilepath(content.getFilename());
if (content.exists != file.exists()) {
content.exists = file.exists();
content.dirty = true;
}
// Is this the currently playing content
if (currentContent != null && content.mp3.equals(currentContent.mp3)) {
if (content.isPlaying != isPlaying) {
content.isPlaying = isPlaying;
content.dirty = true;
}
if (content.isPaused != isPaused) {
content.isPaused = isPaused;
content.dirty = true;
}
}
else {
if (content.isPlaying != false) {
content.isPlaying = false;
content.dirty = true;
}
if (content.isPaused != false) {
content.isPaused = false;
content.dirty = true;
}
}
if (content.dirty) {
DatabaseManager.getInstance().createOrUpdateContent(content);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public class UpdateHolder {
public String mp3;
public int status;
public boolean played;
public float progress;
public boolean exists = false;
public boolean isPlaying = false;
public boolean isPaused = false;
//public int elapsed;
//public int duration;
}
}
I couldn't find the issue in it. I'm stuck here for last 40 hours. Please help. Thank you!
Maybe you are using old version of the library. I found that PullToRefreshAttacher doesn't contain OnRefreshListener. (https://github.com/chrisbanes/ActionBar-PullToRefresh/blob/master/library/src/main/java/uk/co/senab/actionbarpulltorefresh/library/PullToRefreshAttacher.java)
Try to import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener; and use it instead of PullToRefreshAttacher.OnRefreshListener.