Shared element animation between RecyclerView item and CollapsingToolbar within same activity - android

In my application I have a list of items displayed through a RecyclerView adapter. If I click on an item a new Fragment in started within the same Activity. The layout of my item and my Activity look (simplified) like this:
Activity layout:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout>
<ImageView
android:id="#+id/image"
android:transitionName="image" ... />
<android.support.v7.widget.Toolbar ... />
</android.support.design.widget.CollapsingToolbarLayout>
<android.support.design.widget.TabLayout ... />
</android.support.design.widget.AppBarLayout>
<FrameLayout... />
</android.support.design.widget.CoordinatorLayout>
Item Layout:
<RelativeLayout >
<ImageView
android:id="#id/itemImage"
android:transitionName="image" />
<LinearLayout>
<TextView ... />
<TextView ... />
</LinearLayout>
</RelativeLayout>
Now, if the new fragment is started by an item click, I would like to add an animation of the item image to the ImageView in the CollapsingToolbarLayout. I read the article about ShareElement animations but this does not work here because this is not a real ShareElement animation. The target ImageView is not in the new fragment neither I have to start a new activity (I only make the target ImageView visible in the new Fragment). So how would I create such an animation in this case?

So, you are trying to animate a view from one layout to another.
I think this can be achieved using ViewOverlays API. You can see a detailed answer about that API here.
Now in your case, what you'll end up with is you'd add your ImageView to the ViewGroupOverlay of the root layout:
final ViewGroup container = (ViewGroup) findViewById(R.id.content);
container.getOverlay().add(imageView);
...
From the docs:
If the view has a parent, the view will be removed from that parent before being added to the overlay.
Thus, as soon as you perform getOverlay().add(imageView) the view would be removed from it's parent. Now you are free to create your animation and move the imageView to the final destination.
final ViewGroup container = (ViewGroup) findViewById(R.id.content);
container.getOverlay().add(imageView);
// animate imageView by any API, e.g. ViewPropertyHolder
imageView.animate()
.x(finalX)
.y(finalY)
.setDuration(duration)
.setInterpolator(interpolator)
.withEndAction(() -> {
// crucial point, remove the overlay
container.getOverlay().remove(imageView);
// add this `imageView` to the destination layout
destLayout.addView(imageView);
})
Here's a similar feature you're trying to implement:

Related

Is it possible to animate constrained height changes of the recycler view when adding/removing items?

I would like to achieve the behavior when adding or removing the items to/from the recycler view, it changes its height according to the content up to the specified #dimen/maxRecyclerViewHeight value with smooth animation. It's working fine without animation after notifyItemInserted/Removed but the views under the recycler view are 'jumping' so that looks a bit odd. Can I achieve that somehow using TransitionManager.beginDelayedTransition(...)? I appreciate any other ideas.
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constrainedHeight="true"
app:layout_constraintHeight_max="#dimen/maxRecyclerViewHeight"/>
<!-- Other views go under the recycler view -->
</android.support.constraint.ConstraintLayout>
After doing some research I found this can definitely be achieved with TransitionManager.
Let's assume the xml file containing the constraint layout with the recycler view inside is activity_example.xml. So immediately after invoking notifyItemInserted/notifyItemRemoved and notifyItemRangeChanged you can call something like method below:
private void beginDelayedTransition() {
final ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(this, R.layout.activity_example);
// this transition is responsible for animation resizing the view
final Transition transition = new ChangeBounds();
// or any other duration
transition.setDuration(250);
// constraintLayout is a root layout in the activity_example.xml
TransitionManager.beginDelayedTransition(constraintLayout, transition);
constraintSet.applyTo(constraintLayout);
}

Fixed element when using TileView in Android Studio

I am building an activity within Android Studio which uses the TileView library by moagrius to build a layout that can be panned and zoomed, which is all working just fine. I am trying to add an element to the activity which stays fixed at the bottom of the screen and not be affected by the scrolling and zooming of the TileView.
Points to Note:
The TileView and content inside of it are all generated and placed programmatically. It does not use an XML layout.
The element I'm trying to place is an Image. It needs to be in the bottom center of the viewport, and can preferably be reused easily in other activities.
Things I've Tried:
I've tried placing the menu button in its own XML file and tried to use the include attribute on the main XML layout file and inflate that via Java. No luck. The bottom menu is still affected.
I've tried programatically generating the menu button, adding it to a new Relative (tried Linear too) layout, and adding it with parameters to make it sit at the bottom. This got it to appear and stay at the bottom, but it was still affected by zooming and panning.
I've tried using a FrameLayout to add the tileview on top (tried bottom too) of the Relative or Linear layout containing the button, and it was still affected.
I've tried a few other concepts but they didn't work either.
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/res"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
app:theme="#style/Theme.AppCompat">
<include layout="#layout/menu"/>
</RelativeLayout>
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="#+id/bottompanel">
<RelativeLayout android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true">
<ImageView
android:id="#+id/imageView3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#drawable/bottombarclosed"
app:srcCompat="#drawable/bottombarclosed" />
</LinearLayout>
</RelativeLayout>
</FrameLayout>
activity.java
// Initialize TileView, set it's size, and add the detail level (background)
TileView tileView = new TileView(this);
tileView.setSize(5600, 5000);
tileView.addDetailLevel(.15f, "tile-1-1.png", 200, 100);
/*
Generate content for the TileView here.
*/
RelativeLayout rl = new RelativeLayout(this);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
/*
Add the generated content to the Relative Layout for the TileView here.
*/
// Make a new instance of the layout inflator.
LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
// Inflate the bottom menu FrameLayout and save for later.
FrameLayout bm = (FrameLayout) inflater.inflate(R.layout.menu, (ViewGroup) findViewById(R.id.bottompanel));
// Adds the TileView content to the ViewGroup (scaleable, so it can be affected by zooming and panning.)
tileView.addScalingViewGroup(rl);
// Remove the parent from the bottom panel so it can be reassigned. (It crashes without this)
((ViewGroup)bm.getParent()).removeView(bm);
// Add the bottom menu to the tileView without scaling (so it SHOULDN'T be affected by zoom and panning)
tileView.addView(bm);
// Set the content view to display
setContentView(tileView);
Update
I got it to stay in the right position by switching
FrameLayout bm = (FrameLayout) inflater.inflate(R.layout.menu, (ViewGroup) findViewById(R.id.bottompanel));
to
RelativeLayout bm = (RelativeLayout) inflater.inflate(R.layout.menu, (ViewGroup) findViewById(R.id.menulrel)); // menurel is an immediate child of the beforementioned FrameLayout
and by adding the TileView to the RelativeLayout instead of the other way around, and setting the content view to the RelativeLayout (bm). Now the issue is, the menu shows for a second, then is hidden when the TileView updates... Hmmm....
I finally found the answer! I'm just posting it here just in case somebody else stumbles across this issue.
I changed the inflator back to the FrameLayout as it was in the original post.
FrameLayout bm = (FrameLayout) inflater.inflate(R.layout.menu, (ViewGroup) findViewById(R.id.bottompanel));
Then added the tileView to the FrameLayout.
bm.addView(tileView);
Used the full view to locate the Relative Layout container of the menu (parent of the ImageView), then called bringToFront on it.
bm.findViewById(R.id.menurel).bringToFront();
This next step is the MOST IMPORTANT part. You MUST invalidate the RelatieLayout for it to stay present on the screen. Otherwise it fades as the TileView updates and re-renders.
bm.findViewById(R.id.menurel).invalidate();
Then lastly, add set your content view to the new FrameLayout with the other views included.
setContentView(bm);
I hope this helps somebody out in the future!

View invisibility when RecyclerView is empty

I am using a LinearLayout with some views inside. The second last is a RecyclerView, and the last one is an <include> tag linked with a RelativeLayout. I want the last view to be visible permanently, as I want to use it to add items to the RecyclerView.
My problem is that the <include> view below the RecyclerView disappears whenever the adapter is empty. Is there any way to avoid this behaviour?
EDIT
There is a constraint in the way I want the RecyclerView and the "add" View to work together. I would like that it feels as the "add" view is always the last item in the RecyclerView, as it happens in Google Keep lists.
Example Google Keep list example (I cannot add images yet)
You could try to replace your LinearLayout with a RelativeLayout; then you anchor the bottom view to the bottom of the screen, and the RecyclerView would be set above that view. Something like this (just a skeleton):
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent>
<-! other views here -->
<include layout="#layout/something"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:id="#bottomView" />
<RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/some_view_above_recyclerview"
android:layout_above="#+id/bottomView" />
</RelativeLayout>

ImageView in SurfaceView without XML

My question is if is possible add an ImageView in a SurfaceView without XML. If yes, how? I have a main class that has the function of GamePanel, and for apply a Method i need to call it with an ImageView, but i don't know if it is possible. Thanks you in advance.
You need to read about the View and ViewGroups provided by the Android Framework.
I am giving the quick understanding to propose the solution.
Crash Course about View & ViewGroup
At the root of the Android UI system, everything is View.
What is a View?
It is a single widget / UI component that can be displayed on the screen. The View includes Buttons, TextViews, ImageViews, SurfaceView. They can not contain any child view i.e. They can not hold declaration for the any other child view
Following XML definition is incorrect: A view can not hold another view
<SurfaceView
android:id="#+id/textSurfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</SurfaceView>
What is ViewGroup?
Inherited from View and designed to contain and arrange more than one View also called as Child views. The various ViewGroups are LinearLayout, RelativeLayout, FrameLayout etc.
Following XML definition is Correct: A ViewGroup can hold another view
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
Here comes the solution
Step-1: Add a ViewGroup in your XML wrapping the existing SurfaceView. As mentioned already the ViewGroups are LinearLayout, RelativeLayout, FrameLayout etc.
res/layouts/your_layout.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/baseFrame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
Step-2: At the time of view creation add an ImageView to the FrameLayout. onCreate() activity.
setContentView(R.layout.your_layout);
FrameLayout baseFrame = (FrameLayout) findViewById(R.id.baseFrame);
ImageView imageView = new ImageView(this);
imageView.setWidth(/*As per your need*/);
imageView.setHeight(/*As per your need*/);
imageView.setId(/*Any unique positive Number*/ R.ids.imageView1); <= Required to access this view later
/*Set the layout parameters such as layout_gravity as well.*/
baseFrame.addView(imageView);
Step-3: I know you must be wondering about the ImageView Id. I am giving the quicker way to assign an ID to a View.
Create a file ids.xml at res/values
Fill the following details.
<resources>
<item type="id" name="imageView1" />
</resources>
Step-4: Passing an ImageView to the method
ImageView myImageView = (ImageView) findViewById(R.id.imageView1);
methodToBeCalled(myImageView);
I hope that helps.
Happy Coding!!!

Pull down view (menu) from ActionBar

I need to implement a pull-down view that has a "handle" in right-most part of the ActionBar. It should be full width and open with an animation when the handle is clicked, and additionally the handle itself should be draggable. minSdkVersion is 8
Regarding the pull-down functionality itself, I found that SlidingDrawer isn't going to fit the bill, since it has been deprecated as of API v17, and it can only be opened from bottom to top. The control SlidingTray seems to overcome that issue. I haven't tested it thoroughly but but it seems to work as expected.
Now to the main issue. Is it even possible to display the view in such a manner? I have tried to set a custom view for the ActionBar, where the inflated XML looks something like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<my.package.drawer.SlidingTray
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:content="#+id/content"
android:handle="#+id/handle" >
<ImageView
android:id="#+id/handle"
android:layout_width="88dp"
android:layout_height="44dp"
android:src="#drawable/ic_launcher" />
<Button
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</my.package.drawer.SlidingTray>
</RelativeLayout>
Now, the SlidingTray view itself is functioning as expected when I put it in the activity/fragment layout (can drag the handle and click it to open/close the tray), but when inflating the layout above inside the ActionBar, and upon pressing/dragging the handle, the Tray only moves a few pixels before stopping - it doesn't go beyond the ActionBar bounds. This is the main issue - can the view go beyond the ActionBar itself (over the activity displayed below), and if so - how?
Since no one answered, I'll post how I resolved the issue.
After some inspecting with hierarchyviewer, I saw that the ActionBar was in a LinearLayout and as such, it wouldn't be possible to extend a child of it outside of the ActionBar bounds. So I decided to get the root (decor) view and attach the modified version of SlidingDrawer there. Here is the excerpt:
ViewGroup decor = (ViewGroup) getWindow().getDecorView();
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View drawerContainer = inflater.inflate(R.layout.sliding_drawer, null);
drawer = (SlidingDrawer) drawerContainer.findViewById(R.id.drawer);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
decor.addView(drawerContainer, params);
Since adding a view this way displays it behind the Status bar, I also added a container view with top padding of 25dp so that the handle and content are displayed beneath it.
Note: if you are using the SlidingMenu library, you need to do this in onPostCreate(), because the library also does this and will place your view behind all of the other content.

Categories

Resources