SharedElements undesirable effect in RecyclerView - android

I have one activity with one RecyclerView (SuperSlim library) and a detail activity, when I click a item of that list, detail activity will be open. The problem is when I go back, I'm trying to set the clicked element as the first visible element in the list but I get this horrible animation:
This is my onActivityReenter()
#Override
public void onActivityReenter(int resultCode, Intent data) {
super.onActivityReenter(resultCode, data);
Log.d(TAG, "onActivityReenter() called with: resultCode = [" + resultCode + "], data = [" + data + "]");
mTmpReenterState = new Bundle(data.getExtras());
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ITEM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ITEM_POSITION);
mRecycler.scrollToPosition(currentPosition);
postponeEnterTransition();
mRecycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mRecycler.getViewTreeObserver().removeOnPreDrawListener(this);
// TODO: figure out why it is necessary to request layout here in order to get a smooth transition.
mRecycler.requestLayout();
startPostponedEnterTransition();
return true;
}
});
}
And my SharedElementCallback:
private final SharedElementCallback exitTransitionCallBack = new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
if (mTmpReenterState == null) {
// If mTmpReenterState is null, then the activity is exiting.
View navigationBar = findViewById(android.R.id.navigationBarBackground);
View statusBar = findViewById(android.R.id.statusBarBackground);
if (navigationBar != null) {
names.add(navigationBar.getTransitionName());
sharedElements.put(navigationBar.getTransitionName(), navigationBar);
}
if (statusBar != null) {
names.add(statusBar.getTransitionName());
sharedElements.put(statusBar.getTransitionName(), statusBar);
}
} else {
int startingPosition = mTmpReenterState.getInt(EXTRA_STARTING_ITEM_POSITION);
int currentPosition = mTmpReenterState.getInt(EXTRA_CURRENT_ITEM_POSITION);
if (startingPosition != currentPosition) {
// If startingPosition != currentPosition the user must have swiped to a
// different page in the DetailsActivity. We must update the shared element
// so that the correct one falls into place.
sharedElements.clear();
sharedElements.put("number", mLayoutManager.findViewByPosition(currentPosition).findViewById(R.id.text_number));
sharedElements.put("day", mLayoutManager.findViewByPosition(currentPosition).findViewById(R.id.text_day));
sharedElements.put("recycler", mLayoutManager.findViewByPosition(currentPosition + 1).findViewById(R.id.recycler));
}
mTmpReenterState = null;
}
}
};
I think the problem is that the activity try to make an animation from the original item position to the top to the list, but I don't know how avoid that.
Does anyone know how to fix this??
Thanks in advance guys!!!

After a while I've realized that the problem that I was updating the Main Activity list with a notifyItemChanged(int) by LocalBroadcast and the standard recyclerView animation made that glitch. I solve the problem using:
RecyclerView.setItemAnimator(null)
because the standard didn't work
((SimpleItemAnimator) RecyclerView.getItemAnimator())
.setSupportsChangeAnimations(false)
So the problem is nothing to do with SharedElements.

Related

How to program to repaint every second item in recyclerview to a different color? (LinearLayout)

My RecyclerView is having some elements in it. Every second element should be painted in a different color. This is my function.
val BackgroundElemnt: LinearLayout = itemView.findViewById(R.id.container1)
fun bind(currencyPost: CurrencyPost){
if (BackgroundElemnt.get(position)%2 == 0){
BackgroundElemnt.setBackgroundColor(0xB8B8B8)
}
else
{
BackgroundElemnt.setBackgroundColor(0xFFFFFF)
}
CName.text = currencyPost.Name
CValue.text = currencyPost.Value
}
Solution 1:
you should try this
val backgroundElement: LinearLayout = itemView.findViewById(R.id.container1)
fun bind(currencyPost: CurrencyPost){
if (position%2 == 0){
backgroundElement.setBackgroundColor(0xB8B8B8)
}
else
{
backgroundElement.setBackgroundColor(0xFFFFFF)
}
CName.text = currencyPost.Name
CValue.text = currencyPost.Value
}
I replaced if (BackgroundElemnt.get(position)%2 == 0) with if (position%2 == 0) because you're checking for the position of the item in the adapter not the position of the root view.
Solution 2:
You should create a flag in your model that decides what the colour should be, then you should modify your list item set the colour for every second item.
Example:
data class CurrencyPost(val id: Int, val name: String, val hasDifferentColour: Boolean = false)
assuming that is the model of the item, you should then convert your list item to indicate what colour it should be. So before you call adapter.submitList(listOfCurrencyPost)
you should map the items to show the colour
so
val modifiedListOfCountryPost =
listOfCurrencyPost.mapIndexed { countryPost, index ->
if (index % 2 == 0) countryPost.copy(hasDifferentColour = true)
}
then adapter.submit(modifiedListOfCountryPost)
in the onBind method of your adapter you should then do this
fun bind(currencyPost: CurrencyPost){
if (countryPost.hasDifferentColur){
BackgroundElemnt.setBackgroundColor(0xB8B8B8)
}else
{
BackgroundElemnt.setBackgroundColor(0xFFFFFF)
}
CName.text = currencyPost.Name
CValue.text = currencyPost.Value
}
Integer timeOut = 1000;
public void AlterColor(){
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// put your condition statement here
finish();
}
},timeOut);
try this out...placae it in your class and call it within the main method.

Android pagination boundary callback clarification

I've integrated the pagination component in my app and it's working perfectly fine (almost).
I've Database + network model. Database initially has some items which are consumed by LivePagedListBuilder. I observe this LiveData<PagedList<InboxEntity>> and ultimately feed the list to PagedListAdapter#submitList(PagedList<T> pagedList), something like :
LiveData<PagedList<Entity>> entities;
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(5)
.setEnablePlaceholders(false)
.setPageSize(10)
.setInitialLoadSizeHint(10)
.build();
entities = new LivePagedListBuilder<>(DAO.getItemList(), pagedListConfig)
entities.observe(this, new Observer<PagedList<Entity>>() {
#Override
public void onChanged(#Nullable PagedList<Entity> inboxEntities) {
inboxAdapter.submitList(inboxEntities);
isFirstLoad = false;
}
});
DAO#getItemList returns DataSource.Factory<Integer, Entity>.
I am listening for the boundary callback and trigger a network call when it reaches the end of the paged list. That call populates the database again.
There's one more thing. I've registered AdapterDataObserver on recycler view because if an item has been inserted at the beginning, I've to scroll to the top position:
RecyclerView.AdapterDataObserver adapterDataObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (positionStart == 0) {
layoutManager.scrollToPositionWithOffset(positionStart, 0);
}
}
};
I am facing a problem in this model :
After making the network call, database is populated again and onChanged function is called with a new PagedList<Entity>. Now, does this paged list contains only the new items. I've confirmed this.
But onItemRangeInserted method is called with positionStart as 0 too, which suggests that items are being inserted at the beginning. But they are not. They are being inserted at the end, confirmed with stetho db inspector.
Then why is the onItemRangeInserted being called with positionStart as 0? This is making it difficult for me to distinguish when a fresh item is inserted at the beginning of the adapter and when items are inserted at the end.
Edit:
value of itemCount is 10 which is my page size.
In DiffCallback, I just compare the primary key column of the two entities in areItemsTheSame function.
This is how I'm doing it, not ideal but it works. I'm using a recycler view with reverse set to true as it's a chat app so you'll have to adjust for your use case.
In your fragment create a Timer field:
private Timer mTimer;
Add a scroll state listener that sets a "latest row id" that you can compare later.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
//Get the latest id when we started scrolling
AppExecutors.getInstance().databaseIO().execute(() ->
mLatestMessageId = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname));
}
}});
Use the AdapterDataObserver to kick off a new timer when the insert is called.
private RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mTimer != null) {
mTimer.cancel();
}
mTimer = new Timer();
mTimer.schedule(new CheckNewMessageTimer(), 100);
}
};
Here's the timer class, if new items have been inserted the "latest row id" will have incremented in the database. I'm only scrolling to the bottom if I'm already at the bottom, otherwise I just change the color of a "scroll to bottom" FAB that I have.
class CheckNewMessageTimer extends TimerTask {
#Override
public void run() {
if (newItemsAdded()) {
int firstItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
boolean atBottom = firstItemPosition == 1;
SurespotLog.d(TAG, "CheckNewMessageTimer, firstItemPosition: %d, atBottom: %b", firstItemPosition, atBottom);
AppExecutors.getInstance().mainThread().execute(() -> {
if (atBottom) {
SurespotLog.d(TAG, "CheckNewMessageTimer, scrolling to bottom");
mListView.scrollToPosition(0);
}
else {
if (mFab != null) {
SurespotLog.d(TAG, "CheckNewMessageTimer, new message received but we're not scrolled to the bottom, turn the scroll button blue");
mFab.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.surespotBlue)));
}
}
});
}
}
private boolean newItemsAdded() {
int dbLatestMessageID = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname);
if (mLatestMessageId > 0 && dbLatestMessageID > mLatestMessageId) {
mLatestMessageId = dbLatestMessageID;
return true;
}
return false;
}
}

swipelayout working but no feed

I am using swipelayout from daimajia.I get some data from services.When the service has a date than will visible an icon that will open the left sideBut it do nothing when clicking the icon.I have no error that's the reason why cannot resolve it.The action is when clicking the icon,that visible a textview and start a chronometer.
Here is my swipeListener
private SwipeListener swipeListener = new SwipeListener() {
#Override
public void onStartOpen(SwipeLayout layout, SwipeLayout.DragEdge edge) {
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
**viewHolder.discountText.setTimeByTag().play();**
LogUtils.LogE("Left Open...");
}
}
#Override
public void onOpen(SwipeLayout layout, SwipeLayout.DragEdge edge) {
super.onOpen(layout, edge);
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
viewHolder.discountText.setTimeByTag().play();
LogUtils.LogE("Left Open...");
}
}
#Override
public void onClose(SwipeLayout layout, SwipeLayout.DragEdge edge) {
if (edge == SwipeLayout.DragEdge.Left && viewHolder.discountText != null) {
viewHolder.discountText.stop();
LogUtils.LogE("Left Close!");
}
}
};
public void openLeft() {
LogUtils.LogE("onClick openLeft-");
if (getSwipeLayout() == null){
LogUtils.LogE("onClick -null-");
return;
}
if (!getSwipeLayout().isOpen()) {
getSwipeLayout().open(true, SwipeLayout.DragEdge.Left);
LogUtils.LogE("onClick --");
}
}
all the codes working in other my fragments.I need some idea what's the reason that it not give true action that swipe to left.
EDIT:
Here is the Logs:
In this picture I have 2 items there which it's in my favorite page.The last one item has end date that will be visible the icon(ImageView).When pressing the icon,it should swipe to left side but it's not working and give the result like this picture which I'm shared.
EDIT
There is something more.I had looked the codes in debug mode and give here an message:
but put define swipeLayout into my code:
sl.setShowMode(SwipeLayout.ShowMode.LayDown);
sl.setDragEdges(SwipeLayout.DragEdge.Left, SwipeLayout.DragEdge.Right);
sl.setBottomViewIds(R.id.productBottomLeft, R.id.productBottomRight, SwipeLayout.EMPTY_LAYOUT, SwipeLayout.EMPTY_LAYOUT);
TextView button = SwipeLayout.findViewById(R.id.yourButtonId) //the bottom view;
button.setOnClickListener(v -> yourMethod());

SwipeListView setSwipeMode none is disabling onclick as well as swipe

Hi am using a SwipeListView that has two buttons on its back view i'm trying to programatically set the SwipeListView so that it doesn't swipe by setting the swipe mode to none. the problem i am having is the front view is now not registering the click. Does any one know why???
Heres what i have tried so far
final SwipeListView messagesList = (SwipeListView)v.findViewById(R.id.list);
if(r_deleteMessages == false || r_markMessages == false) messagesList.setSwipeMode(SwipeListView.SWIPE_MODE_NONE);
if(messageData != null){
ViewGroup.LayoutParams params = messagesList.getLayoutParams();
int size;
size = messageData.size();
params.height = (SM_Global.pxFromDp(context, 80) * size) +3;
messagesList.setLayoutParams(params);
messagesList.requestLayout();
messagesList.setFocusable(false);
final MessagesAdapter messagesAdapter = new MessagesAdapter(context, R.layout.layout_message_item, messageData,messagesList,"profile");
messagesList.setAdapter(messagesAdapter);
Log.v("Auth","CAN READ MESSAGES | " + r_readMessages);
messagesList.setSwipeListViewListener(new BaseSwipeListViewListener() {
#Override
public void onClickFrontView(int position) {
super.onClickFrontView(position);
Log.v("Auth","CLICKED ");
}
});
You could add a Listener to the list view and override onChangeSwipeMode
if(r_deleteMessages == false || r_markMessages == false){
mList.setSwipeListViewListener(new BaseSwipeListViewListener() {
#Override
public int onChangeSwipeMode(int position) {
return SwipeListView.SWIPE_MODE_NONE;
}
});
}
This way you will still get touch events.
You may also need to do mList.setSwipeOpenOnLongPress(false); to disablr the long click
see: https://github.com/47deg/android-swipelistview/issues/9
I tried the suggested ways but didn't work for me. So I made changes in my List Adapter & handled the click event there. Now it is working fine.

Android accessibility viewPager read two views

I'm using Talkback to read the content of the views on my ViewPager, and it's reading the content of the current view and the content of the next view (not visible).
For example
View 1
TextView -> hi1
TextView -> bye1
View2
TextView -> hi2
TextView -> bye2
Talkback read hi1, hi2, bye1, bye2
I've tried to change the value of pager.setOffscreenPageLimit(), but it doesn't do anything, Talkback always read the current view and the next one, even if the value of OffScreenPageLimit is 4 (it should read the next 2 views).
The only info I've found is that: https://code.google.com/p/eyes-free/issues/detail?id=139
Any idea?
You can create event custom Accessibility event inside the OnChangePageView and save the string you want to read in a list and get position from interface between Activity/fragment and adapter.
ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrollStateChanged(int arg0) { }
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) { }
#Override
public void onPageSelected(int position) {
if(isTalkbackActive(getApplicationContext())) {
AccessibilityEvent event = AccessibilityEvent.obtain();
if (onPageChangeInterface != null) {
String text = onPageChangeInterface.getTextToRead(position);
event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(text);
viewPagerWallets.requestFocus();
viewPagerWallets.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
accessibilityManager.sendAccessibilityEvent(event);
}
}
}
};
Then the same string that you saved you add it in ContentDescription of the relative that you have all the view only in the first position.
ArrayList readAccessibilityValues = new ArrayList<>();
String valor = valor + txtView1.getText()+",";
valor = valor + txtView2.getText()+",";
if(position == 0) {
relativeGeneral.setContentDescription(valor);
}
readAccessibilityValues.add(valor);
I think it's not the best solution but I have not found another
This could help you to get into the right direction:
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val event = AccessibilityEvent.obtain()
event.eventType = AccessibilityEvent.TYPE_ANNOUNCEMENT
event.text.add("Hello TalkBack!")
val accessibilityManager = requireContext().getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
accessibilityManager.sendAccessibilityEvent(event)
}
})
Simply replace "Hello Talkback!" by any string you would like to be read when changing a page in ViewPager.

Categories

Resources