How to swapping ListView items with animation? - android

I want to implement swapping ListView item with animations like in XE Currency Application
from the list when user tap on GBP-British Pound that row will go up side and fit upon INR - Indian Rupee with animations and row INR - Indian Rupee will replace on place of GBP-British Pound
I have tried one animation in listview (used header in listview) then it is working perfect, but the issue is that the header is also scrolled up and down with listview and I want the header view (or any view) fixed at top and below it list can be scrolled
I have tried one fix view at top in Relative Layout and keep listview below the top view at that time animation is worked but inside only listview not outside of listview
how can we implement that in android ?

Why reinvent the wheel? There's out a couple of well-documented and well-structured libraries for dealing with ListView. For example ListViewAnimations is the best one, IMO.
Features
Appearance animations for items in ListViews, GridViews, other AbsListViews
Built in animations include Alpha, SwingRightIn, SwingLeftIn, SwingBottomIn, SwingRightIn and ScaleIn.
Other animations can easily be added
StickyListHeaders is supported, other implementations can easily be added.
Swipe-to-Dismiss, Swipe-To-Dismiss with contextual undo
Drag-and-Drop reordering This is what you need
Animate addition of items
Smoothly expand your items to reveal more content

I had pretty similar problem a while ago. I needed to have a page, where I can re-arrange entries (musical song tracks). So here's my implementation:
DynamicListView (too long to post it here)
My AllTracksFragment, that allows to re-order tracks
public class AllTracksFragment extends SupportFragmentBase {
DynamicListView allTracksListView;
private ArrayList<Track> allTracksList = new ArrayList<>();
TracksListViewAdapter allTracksAdapter;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_all_tracks, container, false);
setHasOptionsMenu(true);
allTracksListView = (DynamicListView)rootView .findViewById(R.id.allTracksListView);
Track track1 = new Track(); // Track is simple model class
track1.trackName = "Winter\'s Coming (Acoustic) 1";
track1.trackId = "47580057";
Track track2 = new Track();
track2.trackName = "Winter\'s Coming (Acoustic) 2";
track2.trackId = "47580057";
Track track3 = new Track();
track3.trackName = "Winter\'s Coming (Acoustic) 3";
track3.trackId = "47580057";
allTracksList.add(track1);
allTracksList.add(track2);
allTracksList.add(track3);
allTracksAdapter = new TracksListViewAdapter(allTracksList, eventBus);
allTracksListView.setTracksList(allTracksList); //SEE DynamicListView class
allTracksListView.setAdapter(allTracksAdapter);
allTracksListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
return rootView;
}
}
And the AllTracksFragment layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.myapp.views.DynamicListView
android:id="#+id/allTracksListView"
android:layout_marginTop="12dp"
android:scrollbars="none"
android:divider="#null"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
TracksListViewAdapter (if needed):
public final class TracksListViewAdapter extends BaseListViewArrayAdapter<PlayTrackView, Track> { // extended version of simple BaseAdapter
final int INVALID_ID = -1;
public TracksListViewAdapter(final List<Track> tracks) {
super(tracks == null ? new ArrayList<Track>(0) : tracks);
if (tracks != null) {
for (int i = 0; i < tracks.size(); ++i) {
mIdMap.put(tracks.get(i), i);
}
}
}
public PlayTrackView createNewView(final Context context, final int position) {
return new PlayTrackView(context); // PlayTrackView - is an extension of FrameLayout
}
HashMap<Track, Integer> mIdMap = new HashMap<>();
#Override
public long getItemId(int position) {
if (position < 0 || position >= mIdMap.size()) {
return INVALID_ID;
}
Track item = (Track) getItem(position);
return mIdMap.get(item);
}
#Override
public boolean hasStableIds()
{
return android.os.Build.VERSION.SDK_INT < 20;
}
}
PlayTrackView.java
public class PlayTrackView extends FrameLayout implements IItemDisplayer<Track> {
public PlayTrackView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.play_track_view, this);
}
public PlayTrackView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.play_track_view, this);
}
public PlayTrackView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.play_track_view, this);
}
#Override
public void displayItem(Track track) {
}
}
Track.java
public class Track {
public String trackId;
public String trackName;
}
IItemDisplayer interface
public interface IItemDisplayer<TItem> {
public void displayItem(TItem item);
}
BaseListViewAdapter
BaseListViewArrayAdapter

Related

Android ListView items displayed in wrong order

I am having an issue updating the ListView in my Android application. I have searched for the solution and read multiple answers but none solved my issue:
android-listview-repeating-old-data-after-refresh
android-requestlayout-improperly-called
android-listview-not-refreshing-after-notifydatasetchanged
android-listview-getview-being-called-multiple-times-on-unobservable-views
Issue
I have a listview with 2 items displayed like this:
Item 1 (position 0)
Item 2 (position 1)
After reloading the data from the source I get the same 2 items, but in the listview it is displayed like this:
Item 2 (position 0)
Item 2 (position 1)
However, when I click on the position 0 in new list it shows correct data of Item 1 (click on position 1 it also shows correct data of Item 2).
The problem is that it displays Item 2 on position 0 and on position 1 (twice).
Here is the code where list is updated and adapter is setup:
public class FishTankFragment extends DeviceFragment {
...
private final List<FishTankStatus.Schedule> schedulesList = new ArrayList<>();
private ScheduleAdapter scheduleAdapter;
...
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
scheduleAdapter = new ScheduleAdapter(view.getContext(), schedulesList);
screenBinding.lvSchedules.setAdapter(scheduleAdapter);
screenBinding.lvSchedules.setOnItemClickListener((parent, view1, position, id) -> {
new ScheduleItemClickListener(this.getContext(), schedulesList.get(position), position);
});
...
}
#Override
public <T> void onResponse(T responseObject) {
...
schedulesList.clear();
schedulesList.addAll(data.getSchedules());
scheduleAdapter.notifyDataSetChanged();
...
}
Here is Adapter code:
public class ScheduleAdapter extends BaseAdapter {
private ScheduleItemBinding itemBinding;
private final List<FishTankStatus.Schedule> schedules;
private final Context context;
public ScheduleAdapter(#NonNull Context context, #NonNull List<FishTankStatus.Schedule> objects) {
this.context = context;
schedules = objects;
}
#Override
public int getCount() {
return schedules.size();
}
#Override
public FishTankStatus.Schedule getItem(int position) {
return schedules.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
itemBinding = ScheduleItemBinding.inflate(LayoutInflater.from(context));
view = itemBinding.getRoot();
}
if (!schedules.isEmpty()) {
String start = StringUtils.printTime(schedules.get(position).getStart());
String end = StringUtils.printTime(schedules.get(position).getEnd());
itemBinding.tvScheduleStart.setText(start);
itemBinding.tvScheduleEnd.setText(end);
FishTankStatus.Schedule schedule = schedules.get(position);
for (String device : schedule.getDevices()) {
switch (device) {
case "something":
itemBinding.ivYellowlightIcon.setVisibility(View.VISIBLE);
break;
case "something 1":
itemBinding.ivBluelightIcon.setVisibility(View.VISIBLE);
break;
case "something 2":
itemBinding.ivAirIcon.setVisibility(View.VISIBLE);
}
}
if (schedules.get(position).getActive()) {
ColorStateList white = ColorStateList.valueOf(
view.getResources().getColor(R.color.white, view.getContext().getTheme()));
itemBinding.lySchedule.setBackground(ResourcesCompat.getDrawable(view.getResources(),
R.drawable.rectangle_p_light_8,
view.getContext().getTheme()));
...
}
}
return view;
}
}
ListView has width and height set to match_parent in parent ConstraintLayout where width=0dp (has parent) and height=match_parent
See the video:
screen recording
Thank you for all the help.
I debugged the app. After clearing schedulesList.clear() it contained 0 items in Fragment and also in BaseAdapter. After addAll items from the source it contained correct items in schedulesList both in Fragment and BaseAdapter.
I tried to fill the data in Adapter as separate List object using clear and addAll.
I will answer my own question for the future visitors...
Just use RecyclerView
It solved all my issues. But I still do not know why the above problem happened.

Recyclerview GridLayoutManger - how to change order of items? Displaying two lists next to each other

Basically I have 2 lists, 1 to 10, and I want to display them in recyclerview one next to the other, something like this:
1 1
2 2
3 3
. .
. .
10 10
What I have tried is GridLayoutManager with 2 columns, but what I'm getting is this:
You can see it's populating grid from left to right, but I want it to go from top to bottom and when it gets to the new list, displays it in the 2nd column.
Any advice how can i make that?
I'm using recyclerviewMergeAdapter because I thought it's a good approach. If there's better solution in this situation let me know.
private final PolazakAdapterRv adapter1;
private final PolazakAdapterRv adapter2;
private final RecyclerViewMergeAdapter mergeAdapter;
#BindView(R.id.recyclerViewWorkDays)
RecyclerView recyclerView;
public DaysFragmentView(Context context) {
super(context);
inflate(getContext(), R.layout.fragment_work_days, this);
ButterKnife.bind(this);
mergeAdapter = new RecyclerViewMergeAdapter();
adapter1 = new PolazakAdapterRv(getContext());
adapter2 = new PolazakAdapterRv(getContext());
mergeAdapter.addAdapter(adapter1);
mergeAdapter.addAdapter(adapter2);
recyclerView.setAdapter(mergeAdapter);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 2);
recyclerView.setLayoutManager(gridLayoutManager);
}
public void setRecyclerViewAdapter(List<Integer>[] lists) {
List<Integer> list1 = lists[0];
List<Integer> list2 = lists[1];
adapter1.setMap(list1);
adapter2.setMap(list2);
mergeAdapter.notifyDataSetChanged();
Timber.d("RecyclerView updated!");
}
PolazakAdapterRv:
public class PolazakAdapterRv extends RecyclerView.Adapter<PolazakHolder> {
private final Context context;
private List<Integer> list;
public PolazakAdapterRv(Context context) {
this.context = context;
this.list = new ArrayList<>();
}
public void setMap(List<Integer> list) {
this.list = list;
}
#Override
public PolazakHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context)
.inflate(R.layout.rv_polazak, parent, false);
return new PolazakHolder(view);
}
#Override
public void onBindViewHolder(PolazakHolder holder, int position) {
holder.textViewPolazakHour.setText(list.get(position).toString());
}
#Override
public int getItemCount() {
return list.size();
}
All you need is one adapter with two view types and a simple logic for indexing the right thing. Sample code:
public class PolazakAdapterRv extends RecyclerView.Adapter<PolazakHolder> {
private final Context context;
private List<Integer> list1;
private List<Integer> list2;
public PolazakAdapterRv(Context context) {
this.context = context;
}
public void setList1(List<Integer> list) {
this.list1 = list;
}
public void setList2(List<Integer> list) {
this.list2 = list;
}
#Override
public PolazakHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context)
.inflate(R.layout.rv_polazak, parent, false);
return new PolazakHolder(view);
}
#Override
public void onBindViewHolder(PolazakHolder holder, int position) {
int index = position / 2; // assumes there are two lists of equal lengths
int viewType = getItemViewType(position);
if (viewType == TYPE_1) {
holder.textViewPolazakHour
.setText(list1.get(index).toString());
}
else if (viewType == TYPE_2) {
holder.textViewPolazakHour
.setText(list2.get(index).toString());
}
}
#Override
public int getItemCount() {
return list1.size() + list2.size();
}
#Override
public int getItemViewType(int position) {
return position % 2 == 0 ? VIEW_TYPE_1 : VIEW_TYPE_2;
}
Breaking it down:
The one adapter has the two lists you want to display so they all scroll together.
You have two view types that describe each list.
Since you want two columns to show each list separately, logically this just means you alternate what you show in each cell. So when the position is even (left column) you return VIEW_TYPE_1. When the position is even (right column) you return VIEW_TYPE_2.
The total count in the adapter is simply the total count of both lists.
The view layout is the same (I guess) so you can use the same onCreateViewHolder.
Finally, when binding the viewholder, you just need to know which view type you're working with based on the index and which model object to use. So just ask your getViewType method for the right view type to know which list to use, and simply divide the position by 2 for the right index into the list. Whether position is even or odd, int division will yield the proper index into the given list.
Hope that helps!

Dynamically creating XML cards

I'm trying to create some layouts dynamically and then inflate them.
Currently I have some layouts and I'm inflating them by using my own adapter class.
However now I need to create some cards based on data that is generated. I tried using the card class for this like this for example
for (int i = 1; i < 10; i++) {
Card card = new Card(this);
card.setText("Test " + i);
mCards.add(card);
}
I can't get this designed how I would want it tho. So is there a way for me to use the xml setup to do this since I have more design options this way?
at the risk of getting downvoted again I'll post my solution
for now this is just a test to see if it would function the way I want to.
Later on I will have the cards generated from an actual list with data inside of it.
First I created a xml layout with a textview inside it
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:id="#+id/test_content"
android:textSize="47pt"/>
</LinearLayout>
after that I created a adapter that will inflate the layout and set the textview for each item in the list
public class xmlAdapter extends CardScrollAdapter {
private List<Integer> mCards;
private LayoutInflater mInflater;
public xmlAdapter(List<Integer> mCards, LayoutInflater inflater)
{
this.mCards = mCards;
this.mInflater = inflater;
}
#Override
public int getCount() {
return mCards.size();
}
#Override
public Object getItem(int i) {
return mCards.get(i);
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
View mView = view;
view = mInflater.inflate(R.layout.xml_test, viewGroup, false);
TextView text = (TextView) view.findViewById(R.id.test_content);
text.setText("Test " + mCards.get(i));
view.setTag(text);
return view;
}
#Override
public int getPosition(Object o) {
return this.mCards.indexOf(o);
}
}
and in my activity class I've done the following.
in the onCreate()
CreateCards();
mCardScroller = new CardScrollView(this);
mCardScroller.setAdapter(new xmlAdapter(numberCards, getLayoutInflater()));
mCardScroller.setOnItemClickListener(this);
take in mind that this is just a for loop to put some data in the list that is being send to the adapter.
public void CreateCards() {
for (int i = 1; i < 10; i++) {
numberCards.add(i);
}
}

Coding around issues with a functional ListView inside a ScrollView

I have a custom configuration page in my app which just so happens to contain a ListView which you can select/deselect, edit, add to and remove items from. Since the amount of configuration is so large I've had to put it all in a ScrollView
My problem is of course that you cannot have scroll functionality within a view which already has it's own scroll functionality. This means I can't have a scrolling ListView inside a ScrollView.
What I've been trying to do is find the best way of limiting the damage this does. I've seen suggestions that say "You could just create a LinearLayout which grows as you add more children". That would work find by the added effort required to plug in the selectable nature, the reordering & sorting of the list as well as the editing would be a maintanance nightmare.
I've spent the day trying to find a way of measuring the height of each ListView item. Once I can find the size of each item (not just the content but any padding and space between items) on each device I know I can simply change the height of the ListView per item added.
Unfortunately I can't seem to find a way to reliably pull back the height of a listviews child.
(The old chestnut of using a GlobalLayoutListener doesn't help me pull back the padding between items)
final TextView listLabel = (TextView) toReturn.findViewById(R.id.listLabel);
final ViewTreeObserver vto = listLabel.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
listLabel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mListItemHeight = listLabel.getHeight();
}
});
Maybe you're trying to display too many details on your page? You could split the activity in a summary with buttons that lead to multiple one-screen-long activities.
In my experience, users usually prefer an uncluttered and clear view, even if that means having to click once or twice to get to the part they want.
EDIT
Expanding ListView's are you're friend - This LinearLayout expands based on it's content. It allows Dynamic ListView's inside of ScrollView.
public class LinearListView extends LinearLayout {
private BaseAdapter mAdapter;
private Observer mObserver;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
public LinearListView(Context context) {
super(context);
init();
}
public LinearListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LinearListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mObserver = new Observer();
}
public void setAdapter(BaseAdapter adapter) {
if (this.mAdapter != null)
this.mAdapter.unregisterDataSetObserver(mObserver);
this.mAdapter = adapter;
adapter.registerDataSetObserver(mObserver);
mObserver.onChanged();
}
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
}
private int mListSelector = R.drawable.selector_list;
public void setListSelector(int resid) {
mListSelector = resid;
}
private class Observer extends DataSetObserver {
public Observer(){}
#Override
public void onChanged() {
List<View> oldViews = new ArrayList<View>(getChildCount());
for (int i = 0; i < getChildCount(); i++)
oldViews.add(getChildAt(i));
Iterator<View> iter = oldViews.iterator();
removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
final int index = i;
View convertView = iter.hasNext() ? iter.next() : null;
View toAdd = mAdapter.getView(i, convertView, LinearListView.this);
toAdd.setBackgroundResource(mListSelector);
toAdd.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(null, v, index, index);
}
}
});
toAdd.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
if(mOnItemLongClickListener != null) {
mOnItemLongClickListener.onItemLongClick(null, v, index, index);
}
return true;
}
});
LinearListView.this.addView(toAdd, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
super.onChanged();
}
#Override
public void onInvalidated() {
removeAllViews();
super.onInvalidated();
}
}
}

How to get a non scrollable ListView?

I should want a non scrollable ListView, and show the entire ListView. It's because my entire screen is a ScrollView, and I dispatch widgets with a RelativeLayout, so I don't need the ListView scroll.
I set my ui with code, not with xml.
I've used listView.setScrollContainer(false), but it's not work, I don't understand why.
Thanks.
I found a very simple solution for this. Just get the adapter of the listview and calculate its size when all items are shown.
The advantage is that this solution also works inside a ScrollView.
Example:
public void justifyListViewHeightBasedOnChildren (ListView listView) {
ListAdapter adapter = listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
Call this function passing over your ListView object:
justifyListViewHeightBasedOnChildren(myListview);
The function shown above is a modification of a post in:
Disable scrolling in listview
Please note to call this function after you have set the adapter to the listview. If the size of entries in the adapter has changed, you need to call this function as well.
The correct answer is here.
Just assign this listener to your ListView:
listView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
return true; // Indicates that this has been handled by you and will not be forwarded further.
}
return false;
}
});
Don't put a listview in a scrollview, it doesn't work. If you want a list of items that doesn't scroll, it's called a linearlayout.
Depending on what exactly you are trying to do, you may be able to solve your problem by ditching the ScrollView and instead using ListView's addHeaderView(View) and addFooterView(View).
I came up with BaseAdapterUnscrollable. Basically it just adds views to ViewGroup container. Implementation is a bit like BaseAdapter. It’s pretty convenient if you use a few non-scrollable lists like that in you project.
In onCreate:
PeopleAdapter peopleAdapter = new PeopleAdapter(this, personList, containerPeopleLinearLayout);
peopleAdapter.setOnItemClickListener(this);
peopleAdapter.drawItems();
Your specific adapter:
public class PeopleAdapter extends BaseAdapterNonScrollable<Person> {
public PeopleAdapter(Context context, List<Person> items, LinearLayout container) {
super(context, items, container);
}
#Override
public View getView(View container, Person person) {
TextView textView = (TextView) LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, null);
textView.setText(person.getName());
return textView;
}
}
BaseAdapterNonScrollable (just copy):
public abstract class BaseAdapterNonScrollable<T> implements NonScrollable, OnItemClick {
public Context context;
private ViewGroup containerViewGroup;
private List<T> itemObjectList;
private OnItemClick itemClickListener;
public BaseAdapterNonScrollable(Context context, List<T> items, ViewGroup containerViewGroup) {
this.context = context;
this.itemObjectList = items;
this.containerViewGroup = containerViewGroup;
}
#Override
public void drawItems() {
if (containerViewGroup == null || itemObjectList.size() == 0) {
return;
}
if (containerViewGroup.getChildCount() > 0) {
containerViewGroup.removeAllViews();
}
//draw all items
for (int i = 0; i < itemObjectList.size(); i++) {
final int position = i;
final View itemView = getView(containerViewGroup, itemObjectList.get(i));
if (itemView != null) {
containerViewGroup.addView(itemView);
//handle item click event
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (itemClickListener != null) {
itemClickListener.onItemClick(itemObjectList, position);
}
}
});
}
}
}
public void setOnItemClickListener(OnItemClick onItemClick) {
this.itemClickListener = onItemClick;
}
public abstract View getView(View container, T itemObject);
#Override
public void onItemClick(List<?> itemList, int position) {
}
}
Interfaces
public interface NonScrollable {
void drawItems();
}
public interface OnItemClick {
void onItemClick(List<?> itemList, int position);
}
to disable scrolling you can use listview.setEnabled(false)
This also disables row selections.
I have achieve this by passing in physics property into the ListView:
ListView.builder(
---> physics: ScrollPhysics(), <-----
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
return //ListTile or whatever
Note: This ListView is inside of a column with other widgets, and the column is wrapped in SingleChildScrollView

Categories

Resources