Don't show transition if view is not visible - android

I have a list of products, if I click on one, the image of the product is transitioned into the detail screen.
And if I go back, the image is transitioned back to the list.
This works fine.
The problem is that when I scroll down in my detail screen, the image is no longer visible.
But when I go back to the list screen the image is still transitioned, resulting is a buggy transition.
Video example here
I want to achieve something like the Play Store
Where there is no return animation if the image is no longer visible.
Code
Starting detail activity:
Intent intent = new Intent(getActivity(), ProductDetailActivity.class);
intent.putExtra(ProductDetailActivity.EXTRA_PRODUCT, product);
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
productViewHolder.getProductCover(), productViewHolder.getProductCover().getTransitionName()).toBundle();
getActivity().startActivity(intent, options);
In DetailActivity I set the transition name:
coverImageView.setTransitionName(getString(R.string.transition_key_product_cover_with_id, product.getId()));
styles.xml:
<item name="android:windowContentTransitions">true</item>
Any idea how to implement the behaviour I want to achieve?

With the following link you can know if a view inside an scroll is visible or not: https://stackoverflow.com/a/12428154/4097924
Then you can make a simple method to know if your imageView is visible inside the scrollview, similar to this easy example:
public boolean isVisibleInsideScroll(){
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (imageView.getLocalVisibleRect(scrollBounds)) {
// Any portion of the imageView, even a single pixel, is within the visible window
return true;
} else {
// NONE of the imageView is within the visible window
return false;
}
}
Then I see two possible options that I have not proved:
option 1: overwrite the method onBack (and every way to go back if you have another one). Inside the method, you can assign the transition when the element it is visible before leaving the screen:
#Override
public void onBackPressed(){
if(isVisibleInsideScroll()){
coverImageView.setTransitionName(getString(R.string.transition_key_product_cover_with_id, product.getId()));
}
super.onBackPressed();
}
option 2: overwrite the method onScroll (and every time the scrollview is scrolled) you can register or unregister the animation if the view is visible.
The code in this option is similar to the previous one.
Good luck! I like a lot your animation, I saw it in youtube. :)

Related

Override reverse SharedElementTransition when onBackPressed

I have 4 ImageButtons placed in a simple Layout. When I click any of these buttons, a new TabActivity is launched, and the corresponding Tab is selected (0-3). The tabs are styled with an ImageView matching one of the previous buttons each.
EDIT WITH SOME PROGRESS BELOW
TL;DR: How to set a different SharedElementTransition when returning to the calling Activity, so that the "back" transition is not a reverse of the original one, and possibly animate different Views?
I also use a SharedElementTransition to animate the ImageButton into the selected tab, which works when entering the new Activity, but if I navigate through the Fragments of the new Activity, and press the back button from a Fragment different from the one I arrived to, the reverse SharedElementTransition doesn't match the Tab I'm currently on, and it just reverses the transition used to launch the new activity.
I can see this is the expected behavior, but how can I override it? I want the reverse transition to start from the current Tab, not from the Tab I arrived at from the previous Activity.
I hope this ignominious picture helps understanding the question (it shows the behavior I want to obtain -starting new activity from the orange button, navigating to the green tab's fragment, and pressing back returning to the first activity transitioning from the green tab-):
There's no much code involved in the question, but just in case I am launching the new Activity as:
(...)
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(fromActivity, sharedView, transitionName);
fromActivity.startActivityForResult(toIntent, requestCode, options.toBundle());
And then, in the arriving Activity, I set up the TabLayout as follows in the onCreate() method (analogous piece of code for each Tab:
tab0 = (...);
ImageView v0 = (ImageView) tab0.findViewById(R.id.tab_img);
ApiLevelHelper.setImageDrawable(this, v0, R.drawable.menu_btn_0);
tabLayout.getTabAt(0).setCustomView(tab0);
Thanks in advance
EDIT - SOME PROGRESS ACHIEVED
Well, There's still an important problem to solve, but I've managed to get the right Tab transitioning on the reverse animation. In order to achieve this, I have no transition name previously attached to the Tab's ImageViews, and I set the transition name on the onCreate() method of the new activity only to the selected Tab. Then inside the onBackPressed() call, I clear all transition names from all Tab's ImageViews, and set it to the current Tab. This way the reverse animation starts from the correct Tab, but it ends up in the original ImageView's position, instead of the new one.
What I've tried so far:
Clearing transition names on the first activity and setting it just to the ImageView I want to reverse-transition to, inside the onActivityResult(...) method. Sadly enough, the animation conditions seem to be stablished before this method is called.
Staticly referencing the parent activity (knowing this is bad, just testing) and clearing transition names and setting just the one I want before calling super.onBackPressed() from the second Activity. It didn't change anything.
Some code showing how the transition name of the current Tab is set:
//Second Activity
#Override
public void onBackPressed() {
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
for (ImageView v : tabImages) {
v.setTransitionName(null);
}
int selectedTab = mTabLayout.getSelectedTabPosition();
tabImages[selectedTab].setTransitionName(getString(R.string.transition_menu_tab));
Intent output = new Intent();
output.putExtra("selected_tab", selectedTab);
setResult(RESULT_OK, output);
}
super.onBackPressed();
}
And then, in the first Activity...:
//First Activity
//This code has no effect right now on the ending position of the transitioned view.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_WHICH_TAB && data != null) {
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
int selectedTab = data.getIntExtra("selected_tab", 0);
setTransitionName(selectedTab); //This call nullifies all transition names
//and sets it back to the right ImageView.
//But it doesn't work.
}
}
super.onActivityResult(requestCode, resultCode, data);
}
I think I somehow need to manipulate the previously calculated transform... still investigating.

Android: Z-Order with a screen-wide clickblocker-view

Edit: Solved. The problem disappeared when I made all my custom views override Button instead of View.
I'm trying to programmatically create a screen-sized invisible view which should capture all click events, thereby blocking all underlying elements. But I'm having some trouble.
The intended goal is that when the user clicks a certain button, a menu is created. While this menu is up, the other buttons that were on the page should no longer function. Clicking on the menu should make stuff happen, clicking anywhere else should make the menu disappear and make the other elements work again.
I've managed to create the screen wide view just fine, and it captures clicks and destroys the menu whenever the user clicks a point that is not already covered by a button. When the user does click on a spot that contains a button however, only the button's onclickevent is handled, and not that of my clickblocker. It should be the other way around.
This leads me to think it's a z-order problem (but not 100% sure).
Unfortunately I'm trying to target older versions, which don't have support for view.setZ(), and view.bringToFront() doesn't appear to do what I want it to do.
Here's the code for the viewblocker:
public class ClickBlockerView extends View{
public ClickBlockerView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(C.GetWindowWidth(getContext()), C.GetWindowHeight(getContext()));
}
public static ClickBlockerView CreateClickBlocker(Context context){
ClickBlockerView c = new ClickBlockerView(context);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
c.setLayoutParams(params);
c.setId(R.id.clickBlocker);
c.setClickable(true);
return c;
}
}
And this is how I'm calling it (from inside another view):
private void createMenuAndClickblocker(){
ClickBlockerView clickblocker = ClickBlockerView.CreateClickBlocker(getContext());
clickblocker.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
destroyMenu();
}
});
((RelativeLayout) this.getParent()).addView(clickblocker);
clickblocker.bringToFront();
createMenu();
requestLayout();
}
Does anyone have some idea how to fix this? Other solutions to the problem are welcome too. I'd rather not have to use xml though (it would have to be added to every activity)
Update:
Apparently, the z-ordering goes fine with spinners and checkboxes, but not with buttons and my custom views. Odd...
Update 2:
When drawn, the shape is shown to correctly fill up the screen, but it is also drawn below the other custom views, even though it is created later and brought to the front.
Strangely enough, the click functionality has meanwhile completely stopped functioning, even though I didn't change anything in the code for this clickblocker.

What's the correct way of displaying ViewPager after associated ListView's item click?

I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).

Some GridView cells fade and disappear after ShareActionProvider intent

I have a GridView where each cell is a thumbnail, a filename and a checkbox. To populate the grid I use a CustomCursorAdapter:
The CustomCursorAdapter extends CursorAdapter.
I have a floating button that launchs the system camera. If I take a picture, the grid updates correctly with the new cell.
In the action bar I have a delete button. If I check one or more checkboxes and press the delete button the cell is/are correctly deleted.
But then I have also a ShareActionProvider button so you can check multiple cells and share them. And that works. BUT, here comes the problem. Imagine we have 3 or 4 cells. You check a couple of them, share them, and when you come back from the sharing dialog you see all the cells and you can see how all of them fade and disappear but one, the upper leftmost one.
I checked and saw that after the sharing dialog is the onResume method the one executed. The only thing I'm interested to be done in that method is to reset the checkboxes (a selected.clear() of the variable and a cb.setChecked(false) of each Checkbox view). Also I set the intent of the ShareActionProvider button for a empty selection (this will trigger a Toast: "Select first the images to share" if the button is pressed now).
#Override
public void onResume() {
if (shared) { //we come from a share
uncheck(); //unchecks the Checkboxes views
selected.clear(); //reset the selected items list
//getShareIntent returns an intent with the items in
//selected (now empty) as extras
Intent intent = this.getShareIntent();
if (intent != null && mShareActionProvider != null) {
mShareActionProvider.setShareIntent(Intent.createChooser(intent,
getResources().getText(R.string.send_to)));
}
shared = false;
}
super.onResume();
}
Removing the unchecking, reset and new intent setting don't affect to that behaviour, the cells disappear anyway. In fact, if I comment the whole onResume function the problem remains.
I tried with things like:
grid.invalidateViews();
mAdapter.notifyDataSetChanged();
grid.setAdapter(mAdapter);
Also tried and put the super as the first line, but then checkboxes, intent and list variable aren't updated.
Another clue is that if I press the thumbnail of the unique cell left visible (this loads a new activity to see the picture big size), then the disappeared cells appear for a second while the new activity is loading. o_O
I tried to press the places where the thumbnails should appear after the share to check if it launches the big display activity anyway, but that doesn't happen.
I read about similar things when scrolling GridViews but I just have three elements, no need to scroll. What could be happening here?
EDIT: To check if the issue was related to the memory and the thumbnails, I commented them in the item views, letting just the textview and the checkbox. But the problem is still there. Absolutely desperate -I've been days trying to fix this-I tried to relaunch the grid activity in the onResume, to try to force the redraw (while mAdapter.notifyDataSetChanged() worked when adding a new cell, never worked after the sharing):
#Override
public void onResume() {
super.onResume();
if (shared) {
shared = false;
Intent intClearStack = new Intent(
getBaseContext(),
GridActivity.class);
intClearStack
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intClearStack);
finish();
}t
}
I'm not proud of this solution but is the nearest thing I found to fix this. Nearest because, while it works almost all the time, ocasionally it doesn't. I'd like to know why there's a difference when it comes back from a ShareActionProvider activity and any other activity, to try to find out why this is acting like that.

Changing reenter animation to another item of a list

I have a RecyclerView with images and when I press an image the app opens another activity that contains a ViewPager with the same images but in the position of the one I selected.
I've done the transition in Lollipop to share this image between activities using supportPostponeEnterTransition and supportStartPostponedEnterTransition in the called activity to wait until the viewPager is loaded with images to start the transition.
When I enter in the called activity and when I press back the transitions are ok.
The problem I'm facing is if I move to another image in the ViewPager of the called activity, when I press back it animates the image that it was selected at the beginning, not the currently selected one.
I've been able to change the animated image to the one selected in the called activity with this:
#Override
public void onBackPressed() {
View view = ((ImageDetailFragment) adapter.getFragment(viewPager,
viewPager.getCurrentItem())).getTransitionView();
ViewCompat.setTransitionName(view, Constants.TRANSITION_IMAGE);
super.onBackPressed();
}
But it is returning to the same position of the original image in the list of the calling activity.
How can I do it to make the image return to its position in the list of the calling activity?
The first thing to do is to make sure that the views work properly without any Activity transition. That is, when your return from Activity with the ViewPager, the RecyclerView Activity should be showing the View that the ViewPage was showing. When you call the ViewPager activity, use startActivityForResult and use the result to scroll the RecyclerView to the correct position.
Once that is working, the Activity Transition can be made to work. I know that you've given each View in your RecyclerView a different transitionName, right? When you bind the View, call setTransitionName and give it a repeatable name. Typically this is the image URL or cursor row ID or at worst some munged index like "image_" + index.
The next thing you need to do is set the SharedElementCallback for both the calling Activity (exit) and called activity (enter). In each, you're going to need to override the onMapSharedElements callback to remap the shared element.
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// assuming just one shared element, excuse the ugly code
sharedElements.put(names.get(0), mSharedElement);
}
Here, mSharedElement has been set in the onActivityReenter in the calling (RecyclerView) activity and in onCreate and prior to finishAfterTransition (onBackPressed?) in the called (ViewPager) activity.
The onActivityReenter gives new functionality specifically for this case. You can look at the results there before the called Activity completes.

Categories

Resources