I've implemented shared element transitions for my app, where the transition begins on an image from a Fragment (with RecyclerView) inside a ViewPager on the home screen and expands into full screen gallery view, again within a Fragment in a ViewPager. This is all working fine except that if the image is not fully visible it goes on top of the TabBar before expanding into full screen. Here's what's happening:
My enter transition looks like this:
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="#android:id/statusBarBackground"/>
<target android:excludeId="#android:id/navigationBarBackground"/>
</targets>
</fade>
And exit:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
<fade>
<targets>
<target android:excludeId="#android:id/statusBarBackground" />
<target android:excludeId="#android:id/navigationBarBackground" />
</targets>
</fade>
</transitionSet>
And in the shared element callback of the calling activity I've got this:
View navigationBar = activity.findViewById(android.R.id.navigationBarBackground);
View statusBar = activity.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);
}
Finally in styles.xml for the activity theme:
<item name="android:windowContentTransitions">true</item>
<item name="android:windowEnterTransition">#transition/details_window_enter_transition</item>
<item name="android:windowReturnTransition">#transition/details_window_return_transition</item>
I don't really understand how the toolbar (or actionbar) can be excluded by the transition without getting this overlap. Perhaps a way to do it would be to somehow force the image to be clipped at the top part so that it doesn't become fully visible when under the ToolBar and expands only from the visible rectangle.
I've tried adding <target android:excludeId="#id/action_bar_container"/> to the targets of the animation but the same thing happens still.
Any suggestions are welcome.
I found a similar problem in my project.Add below code in your style.
<item name="android:windowSharedElementsUseOverlay">false</item>
It works for me.
I came up with a temporary workaround. Before the shared element transition is executed, the calling activity checks whether the target view is within the bounds of the RecyclerView. If not, the RecyclerView is smooth-scrolled to show the full view and once that is finished the transition runs. If the view is fully visible, then transitions runs normally.
// Scroll to view and run animation when scrolling completes.
recycler.smoothScrollToPosition(adapterPosition);
recycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recycler.getViewTreeObserver().removeOnPreDrawListener(this);
// Open activity here.
return true;
}
});
Here's the result, not too bad until I find a better solution:
I guess i have the proper answer for this question. This issue happening because on the second activity you do not have toolbar at all, so it can not be added to transition as shared element. So in my case i added some 'fake toolbar' to my second activity, which have 0dp height. Then you can add toolbar from first activity as a shared element, and give him change bounds animation, so toolbar will collapse at the same time as image and image will no longer be 'over' toolbar.
My 'fake toolbar' view:
<View
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#color/colorPrimary"
android:elevation="10dp"
android:outlineProvider="none"
android:transitionName="toolbar_transition" />
important notes:
-view have to have non-transparent background
-i added elevation to be sure that my view i 'over' image
-elevation causes shadow, whick i did not wanted, so i set outilenProvider as none
Next all you have to do is add your toolbar to shared elements
sharedElements.add(new Pair<>(toolbar, "toolbar_transition"));
I searched everywhere and couldn't find any solution so I figured myself. Here is a recorded demo (at half the speed) to show the result (with and without the fix).
Please checkout the full working demo here:
https://github.com/me-abhinav/shared-element-overlap-demo
Steps
Let us say we have two activities viz. MainActivity which has a scrolling container with a grid/list of thumbnails, and we have a SecondActivity which shows the image in a slideshow in fullscreen.
Please checkout the full code to completely understand the solution.
Inside your MainActivity which hosts the scrolling container, set a click listener on your thumbnail to open SecondActivity:
ImageView imageView = findViewById(R.id.image_view);
imageView.setOnClickListener(v -> {
// Set the transition name. We could also do it in the xml layout but this is to demo
// that we can choose any name generated dynamically.
String transitionName = getString(R.string.transition_name);
imageView.setTransitionName(transitionName);
// This part is important. We first need to clip this view to only its visible part.
// We will also clip the corresponding view in the SecondActivity using shared element
// callbacks.
Rect localVisibleRect = new Rect();
imageView.getLocalVisibleRect(localVisibleRect);
imageView.setClipBounds(localVisibleRect);
mClippedView = imageView;
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra(SecondActivity.EXTRA_TRANSITION_NAME, transitionName);
intent.putExtra(SecondActivity.EXTRA_CLIP_RECT, localVisibleRect);
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
MainActivity.this,
Pair.create(imageView, transitionName));
startActivity(intent, options.toBundle());
});
Restore the clip in onResume() of your MainActivity.
#Override
protected void onResume() {
super.onResume();
// This is also important. When we come back to this activity, we need to reset the clip.
if (mClippedView != null) {
mClippedView.setClipBounds(null);
}
}
Create transition resource in your res folder like this:
app/src/main/res/transition/shared_element_transition.xml
The contents should be similar to this:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="375"
android:interpolator="#android:interpolator/fast_out_slow_in"
android:transitionOrdering="together">
<!-- This is needed to clip the invisible part of the view being transitioned. Otherwise we
will see weird transitions when the image is partially hidden behind appbar or any other
view. -->
<changeClipBounds/>
<changeTransform/>
<changeBounds/>
</transitionSet>
Set the transition in your SecondActivity.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// Setup transition
Transition transition =
TransitionInflater.from(this)
.inflateTransition(R.transition.shared_element_transition);
getWindow().setSharedElementEnterTransition(transition);
// Postpone the transition. We will start it when the slideshow is ready.
ActivityCompat.postponeEnterTransition(this);
// more code ...
// See next step below.
}
Now we need to clip the shared view in the SecondActivity as well.
// Setup the clips
String transitionName = getIntent().getStringExtra(EXTRA_TRANSITION_NAME);
Rect clipRect = getIntent().getParcelableExtra(EXTRA_CLIP_RECT);
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (int i = 0; i < sharedElementNames.size(); i++) {
if (Objects.equals(transitionName, sharedElementNames.get(i))) {
View view = sharedElements.get(i);
view.setClipBounds(clipRect);
}
}
super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
}
#Override
public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
for (int i = 0; i < sharedElementNames.size(); i++) {
if (Objects.equals(transitionName, sharedElementNames.get(i))) {
View view = sharedElements.get(i);
view.setClipBounds(null);
}
}
super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
}
});
Related
I want to implement shared element transition in my app, when one activity's recycler view item transforms into another activity like here: https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F15N3n1xwTt0briEbfIvFUG01pMv2d_xaT%2F02-focus-focalelement-do.mp4. (source: https://material.io/design/motion/choreography.html#using-a-focal-element)
Namely, the item is fading out and changes bounds then the new activity fade in. As far as I understand it is simple AutoTransition, but it doesn't work. Simple fading doesn't work as well.
Thus, for now I achieve only that the item gets background of new activity an then changes its bounds.
So, I ended up by adding recycler view item's layout in the resulting activity layout. The data (e.g. the title, etc.) of the clicked item is transferred to the next activity with intent.putExtra(). Shared elements in such case will be of course the item's root view and resulting activity's root view. When activity starts I set the data of the item to matching views in activity via SharedElementCallback, e.g.:
setEnterSharedElementCallback(
object : SharedElementCallback() {
override fun onSharedElementStart(...) {
val title = intent.getStringExtra(TITLE)
activity_item_title.text = title
........
}
override fun onSharedElementEnd(...) {
}
})
This allows to show exactly the same item view at the beginning of the transition. Then it should start change its bounds, fading out this item's view at the same time. And at some moment (e.g. in the middle of the transition) when the initial view completely fades out, the laouyt of the activity shows up, fading in gradually. To do this we need to hide item's view in the middle of the transition (View.visibility = View.GONE) and make activity views visible. Probably this is not the best way, but I solve this by adding a listener to shared element enter transition and used Handler().postDelayed:
window.sharedElementEnterTransition.addListener(
object : android.transition.Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {}
override fun onTransitionResume(transition: Transition) {}
override fun onTransitionPause(transition: Transition) {}
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionStart(transition: Transition) {
Handler().postDelayed({
activity_item.visibility = View.GONE
activity_view_1.visibility = View.VISIBLE
activity_view_2.visibility = View.VISIBLE
.............
.............
// Also you could e.g. set the background to your activity here, ets.
activity_view_root.background = backgroundDrawable
}, 150) //suppuse that the whole transition length is 300ms
}
}
})
The transition animations themselves could look as follows:
<transitionSet>
<targets>
<target android:targetId="#id/activity_root_view"/>
</targets>
<transition
class="com.organization.app.utils.FadeOutTransition"
android:duration="150"/>
<transition
class="com.organization.app.utils.FadeInTransition"
android:startDelay="150"/>
<changeBounds android:duration="300"/>
</transitionSet>
Here, custom FadeOutTransition and FadeInTransition were used since simple android <fade/> animation doesn't work with shared elements. These classes are similar to that given in the answer here: Why Fade transition doesn't work on Shared Element.
The steps for creating return transition are similar.
I want to disable all the animations that happen when launching a new activity in my android app (for all the activities). Is there a way to achieve this once and for all? Or should I go to each and every activity and use Intent.FLAG_ACTIVITY_NO_ANIMATION or overridePendingTransition or both?
You can use style if you want:
<style name="noAnimTheme" parent="android:Theme">
<item name="android:windowAnimationStyle">#null</item>
</style>
And set them for your activity:
<activity android:name=".ui.ArticlesActivity" android:theme="#style/noAnimTheme">
</activity>
Let me know if thats what you meant...
Credit to #Santosh https://stackoverflow.com/a/9312957/3180983
When I built my app, I used only one activity. On the activity there was 4 Custom views. Each custom view represent another "Activity" its not really activity... Im playing with few custom view so each one is another window...
Here is the code with animation (*** IF you don't want animation SKIP THIS CODE to the next goToRegistrationPage() down below.):
//This code change the view so that the register form will appear. instead of changing activity with animation.
public void goToRegistrationPage()
{
Animation animationRightX1 = AnimationUtils.loadAnimation(this, R.anim.translate_right_x1);
//animationRightX1.
Animation animationRightX2 = AnimationUtils.loadAnimation(this, R.anim.translate_right_x2);
Animation animationRightX3 = AnimationUtils.loadAnimation(this, R.anim.translate_right_x3);
final int width = this.getWindowManager().getDefaultDisplay().getWidth();
layout.MainView.setVisibility(View.GONE);
layout.MainLogin.startAnimation(animationRightX1);
layout.MainRegister.startAnimation(animationRightX1);
animationRightX1.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
layout.layoutScroll.scrollTo((width*2), 0);
layout.MainView.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
And here is the code without the animation (This is what you need):
//This code change the view so that the register form will appear. instead of changing activity
//Go to the registration form from the main view.
public void goToRegistrationPageFromMainView()
{
final int width = this.getWindowManager().getDefaultDisplay().getWidth();
layout.layoutScroll.scrollTo((width*2), 0); // its width*2 because there is the "some other view" so it need to go 2 times width to the right side...
}
So basically what you do here is scrolling windows with width amount of pixels.
layoutscroll is the pink color in the picture.
layout is a class which I created to store all the layouts... its a personal preference you could do this.layoutscroll....
Mainview, other view, and registration form are custom views that extending linearlayout... you can attach to each one of them XML file with linear layout inflater...
Ofcorse that you make the scroll view unscrollable....
On the android material design principles page, one of the examples shows a FAB expanding into a new full screen. (Under "Full Screen")
http://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-transitions
I've tried to implement the same effect in my app, but with little success.
I managed to create a FAB that expands into a view using this code as reference: https://gist.github.com/chris95x8/882b5c5d0aa2096236ba.
It worked, but I was wondering whether I could apply the same effect to an activity transition. I've tried looking it up and playing with it myself but could not find anything that might work.
I know I could make the FAB expand into a Fragment and not a whole new activity, but I'm not sure if that's what being done, and whether that's optimal or not.
And so my question is, is there a way to implement the fab-expanding reveal effect as an activity transition, or is it supposed to just reveal a new fragment?
I am developing an app which expands a FloatingActionButton into a new Activity. I'm not sure that if you like my implementation, but please see pictures at first:
So the first picture shows MainActivity and the last one shows SecondActivity, which is "expanded" from FAB.
Now, I want to mention that I'm not actually expanding a FAB into a new Activity but I can let user feel that the new page is expanded from that FAB, and I think that's enough for both developers and users.
Here's implementation:
Preparation:
A FloatingActionButton of course,
Visit https://github.com/kyze8439690/RevealLayout and import this library to your project. It is used to play reveal animation. It has a custom BakedBezierInterpolator to control reveal animation and make it material-styled.
Steps:
create activity_main.xml like this:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--Your main content here-->
<RevealLayout
android:id="#+id/reveal_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible">
<View
android:id="#+id/reveal_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
</RevealLayout>
</FrameLayout>
find Views:
mRevealLayout = (RevealLayout) findViewById(R.id.reveal_layout);
mRevealView = findViewById(R.id.reveal_view);
expand when user clicks FAB:
mFab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mFab.setClickable(false); // Avoid naughty guys clicking FAB again and again...
int[] location = new int[2];
mFab.getLocationOnScreen(location);
location[0] += mFab.getWidth() / 2;
location[1] += mFab.getHeight() / 2;
final Intent intent = new Intent(MainActivity.this, SecondActivity.class);
mRevealView.setVisibility(View.VISIBLE);
mRevealLayout.setVisibility(View.VISIBLE);
mRevealLayout.show(location[0], location[1]); // Expand from center of FAB. Actually, it just plays reveal animation.
mFab.postDelayed(new Runnable() {
#Override
public void run() {
startActivity(intent);
/**
* Without using R.anim.hold, the screen will flash because of transition
* of Activities.
*/
overridePendingTransition(0, R.anim.hold);
}
}, 600); // 600 is default duration of reveal animation in RevealLayout
mFab.postDelayed(new Runnable() {
#Override
public void run() {
mFab.setClickable(true);
mRevealLayout.setVisibility(View.INVISIBLE);
mViewToReveal.setVisibility(View.INVISIBLE);
}
}, 960); // Or some numbers larger than 600.
}
});
And here is hold.xml in res/anim:
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="960" <!-- Enough-large time is OK -->
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%"/>
</set>
That's all.
Improvements:
RevealLayout has a bug(plays rectangular instead of circular reveal animation) for devices under API 17(Android 4.2), you can add these lines in constructor of it:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
If your SecondActivity contains complicated contents, a simple View used as reveal_view in the layout.xml isn't enough/perfect. You can include the second layout inside the RevealLayout reveal_layout. It seems wasteful and hard to control if the second layout won't appear same at every time. But for me, it will. So you can make other improvements if you should.
If you want to implement totally same animation shown in Material Design Guide, you can set layout_height of the RevealLayout into a specific number instead of match_parent. After expanding animation ends(or some time after the animation plays, which should make the whole process of animation smoothly), then you can animate translationY. The important point is, just cheat users visually by controlling animation duration.
Finally, this is my own experience/attempt and I'm a beginner in developing Android apps. If there are any mistakes/further improvements, please leave comments/edit my answer. Thank you.
I made a custom activity, based on this question Circular reveal transition for new activity , that handle the CircularRevealAnimation and his reverse effect when the activity finish:
public class RevealActivity extends AppCompatActivity {
private View revealView;
public static final String REVEAL_X="REVEAL_X";
public static final String REVEAL_Y="REVEAL_Y";
public void showRevealEffect(Bundle savedInstanceState, final View rootView) {
revealView=rootView;
if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rootView.setVisibility(View.INVISIBLE);
ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
if(viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
circularRevealActivity(rootView);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}
}
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void circularRevealActivity(View rootView) {
int cx = getIntent().getIntExtra(REVEAL_X, 0);
int cy = getIntent().getIntExtra(REVEAL_Y, 0);
float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());
// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, 0, finalRadius);
circularReveal.setDuration(400);
// make the view visible and start the animation
rootView.setVisibility(View.VISIBLE);
circularReveal.start();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: onBackPressed();break;
return super.onOptionsItemSelected(item);
}
}
#Override
public void onBackPressed() {
destroyActivity(revealView);
}
private void destroyActivity(View rootView) {
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)
destroyCircularRevealActivity(rootView);
else
finish();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void destroyCircularRevealActivity(final View rootView) {
int cx = getIntent().getIntExtra(REVEAL_X, 0);
int cy = getIntent().getIntExtra(REVEAL_Y, 0);
float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());
// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, finalRadius, 0);
circularReveal.setDuration(400);
circularReveal.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
rootView.setVisibility(View.INVISIBLE);
finishAfterTransition();
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
// make the view visible and start the animation
rootView.setVisibility(View.VISIBLE);
circularReveal.start();
}
}
You can extend this with your own activity and call in your onCreate the method 'showRevealEffect' like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_activity_layout);
//your code
View root= findViewById(R.id.your_root_id);
showRevealEffect(savedInstanceState, root);
}
You also have to use a transparent theme like this one:
<style name="Theme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">#android:color/transparent</item>
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="colorControlNormal">#android:color/white</item>
</style>
In the end, to launch this activity you should pass via extra the coordinates where the animation should start:
int[] location = new int[2];
fab.getLocationOnScreen(location);
Intent intent = new Intent(this, YourRevealActivity.class);
intent.putExtra(SearchActivity.REVEAL_X, location[0]);
intent.putExtra(SearchActivity.REVEAL_Y, location[1]);
startActivity(intent);
you can use this lib [https://github.com/sergiocasero/RevealFAB][1]
[1]: https://github.com/sergiocasero/RevealFAB 3rd party its easy and simple to use
Add to your layout
<RelativeLayout...>
<android.support.design.widget.CoordinatorLayout...>
<!-- YOUR CONTENT -->
</android.support.design.widget.CoordinatorLayout>
<com.sergiocasero.revealfab.RevealFAB
android:id="#+id/reveal_fab"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fab_color="#color/colorAccent"
app:fab_icon="#drawable/ic_add_white_24dp"
app:reveal_color="#color/colorAccent" />
</RelativeLayout>
Important: This component goes above your content. You can use Coordinator, LinearLayout... or another Relative layout if you want :)
As you can see, you have 3 custom attributes for customizing colors and icon
Setting information about intent:
revealFAB = (RevealFAB) findViewById(R.id.reveal_fab);
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
revealFAB.setIntent(intent);
revealFAB.setOnClickListener(new RevealFAB.OnClickListener() {
#Override
public void onClick(RevealFAB button, View v) {
button.startActivityWithAnimation();
}
});
Don't forget call onResume() method!
#Override
protected void onResume() {
super.onResume();
revealFAB.onResume();
}
Someone investigated the implementation of transition between activities from Plaid. Her example were published via https://github.com/hujiaweibujidao/FabDialogMorph.
Briefly speaking, she transits two activities with:
The FAB as the shared element.
The layout in the target activity with the same android:transitionName as the FAB.
To smooth the animation, MorphDrawable (extended from Drawable) and MorphTransition (extended from ChangeBounds) are implemented and applied.
Question Summary: How can I make a ProgressBar integrated inside the ActionBar, like on the Chrome App?
Details: Look at this screenshot from Chrome:
I want to create an Action Bar just like this. Just under the Action Bar, there's a ProgressBar that fills according to page load. I've seen this example from many apps, like Feedly, but I haven't been able to create my own implementation. I tried using Android's own APIs to create it:
#Override
protected void onCreate(Bundle savedInstanceState) {
//Request Permission to display the Progress Bar...
this.requestWindowFeature(Window.FEATURE_PROGRESS);
this.setWindowContentView(R.layout.activity_main)
super.onCreate(savedInstanceState);
this.setProgressBarIndeterminate(true);
}
But this code only causes the ProgressBar to show over the Action Bar, like so:
So, how can I make my ProgressBar appear under the Action Bar, like on the Chrome App?
I wasn't fully satisfied with the accepted answer above, so I did some extra research myself.
The trick I believe they used is that they retrieved the top view in the view hierarchy called DecorView and added the progress bar in there. That way, the progress bar displays both over the action bar AND the content area. Note that the S.D.'s answer puts the progress bar into content area and 'steals' space from actual content, what can lead to unexpected results.
Sample screenshots of this implementation:
Code
Just put this code into onCreate method of some activity and it should work:
// create new ProgressBar and style it
final ProgressBar progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 24));
progressBar.setProgress(65);
// retrieve the top view of our application
final FrameLayout decorView = (FrameLayout) getWindow().getDecorView();
decorView.addView(progressBar);
// Here we try to position the ProgressBar to the correct position by looking
// at the position where content area starts. But during creating time, sizes
// of the components are not set yet, so we have to wait until the components
// has been laid out
// Also note that doing progressBar.setY(136) will not work, because of different
// screen densities and different sizes of actionBar
ViewTreeObserver observer = progressBar.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
View contentView = decorView.findViewById(android.R.id.content);
progressBar.setY(contentView.getY() - 10);
ViewTreeObserver observer = progressBar.getViewTreeObserver();
observer.removeOnGlobalLayoutListener(this);
}
});
You can play with LayoutParams' height argument to set the progressBar wider or narrower, but you might have to adjust the -10 offset.
Styling
Unfortunately, you can see the ugly gray background of the progress bar. To remove it, simply searching for the background by id and trying to hide it doesn't work. To remove the background, I had to create identical drawble of the system version and remove the background item.
TL;DR: Create file progress_horizontal_holo_no_background_light.xml and paste this drawable:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#android:id/secondaryProgress">
<scale android:scaleWidth="100%"
android:drawable="#drawable/progress_secondary_holo_light" />
</item>
<item android:id="#android:id/progress">
<scale android:scaleWidth="100%"
android:drawable="#drawable/progress_primary_holo_light" />
</item>
</layer-list>
Copy appropriate .png drawables from sdk/platforms/android-xx/data/res/drawable-xxx/ to your project and then in the code you can add:
progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.progress_horizontal_holo_no_background_light));
Extra: Indeterminate Progress Bar
Pre-KitKat versions of indeterminate progress bars are pretty ugly and laggy.
You can download new smooth progressBar called ButteryProgressBar. Just search for it on google (I cannot post any more links, because I am new here :[ ), add the class into your project and you can simply replace the previous ProgressBar with this code and have crispy indeterminate progress bar:
final ButteryProgressBar progressBar = new ButteryProgressBar(this);
progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 24));
You might also need to simplify this code:
final TypedArray ta = c.obtainStyledAttributes(attrs, R.styleable.ButteryProgressBar);
try {
mBarColor = ta.getColor(R.styleable.ButteryProgressBar_barColor,
c.getResources().getColor(android.R.color.holo_blue_light));
mSolidBarHeight = ta.getDimensionPixelSize(
R.styleable.ButteryProgressBar_barHeight,
Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity));
mSolidBarDetentWidth = ta.getDimensionPixelSize(
R.styleable.ButteryProgressBar_detentWidth,
Math.round(DEFAULT_DETENT_WIDTH_DP * mDensity));
} finally {
ta.recycle();
}
to this code:
mBarColor = c.getResources().getColor(android.R.color.holo_blue_light);
mSolidBarHeight = Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity);
mSolidBarDetentWidth = Math.round(DEFAULT_DETENT_WIDTH_DP * mDensity);
Hope I helped :)
Extend Activities from following class to have a ProgressBar at the top(below ActionBar) of each, and a getProgressBar() method:
Parent class:
public abstract class ProgressActivity extends Activity {
private ProgressBar mProgressBar;
#Override
public void setContentView(View view) {
init().addView(view);
}
#Override
public void setContentView(int layoutResID) {
getLayoutInflater().inflate(layoutResID,init(),true);
}
#Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
init().addView(view,params);
}
private ViewGroup init(){
super.setContentView(R.layout.progress);
mProgressBar = (ProgressBar) findViewById(R.id.activity_bar);
return (ViewGroup) findViewById(R.id.activity_frame);
}
protected ProgressBar getProgressBar(){
return mProgressBar;
}
}
Layout (progress.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent">
<ProgressBar android:id="#+id/activity_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-8dp"
style="#android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
/>
<FrameLayout android:id="#+id/activity_frame"
android:layout_width="match_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
This is now a native behavior that can be obtained using SwipeRefreshLayout.
You can wrap your scrollable view with a SwipeRefreshLayout and then you just need to listen to onRefresh events:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
#Override public void onRefresh() {
new Handler().postDelayed(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(false);
}
}, 5000);
}
A nice and simple tutorial can be found in this blog.
I have compiled code from this thread as well as other threads on StackOverflow and created a project that can be used to implement ButteryProgessbar as well as "pull down to refresh". https://github.com/pauldmps/Gmail-like-Pull-Down-to-Refresh
Feel free to use the code in your application.
A big thanks to you guys.
Please use below code. just use one default style in progress bar as style="?android:attr/progressBarStyleHorizontal"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:indeterminate="true"
/>
</RelativeLayout>
Well , I did something similar for one of my pet apps and am not sure if that's the only way or the best way to do it , but it definitely works.
To begin with , use an overlay action bar and then set the background drawable to "null" so that the overlay action bar is transparent.
Now in you activity layout , set the top margin for your root view to "actionbar height" You can do it like this.
<RelativeLayout
...
android:layout_marginTop="?android:attr/actionBarSize" />
Now the action is not going to hide any of your activity content.
The next thing you have to do now is - add a header view on top of your root view with height same as the action bar. Set a background color to it. This would now be the color of your action bar and since the action bar aligns perfectly on top pf this header view.
Now you can easily put a progress bar in the header view and align it to the bottom of the header view. To the user this would look as if the progress bar is on the action bar itself , just like chrome ..
I would like to know if there is a simple way to add a view (a button) to a RelativeLayout, with some kind of scale animation.
I extended a class from Button and did something like this:
public class MyButton extends Button {
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ScaleAnimation anim = new ScaleAnimation(0,1,0,1);
anim.setDuration(1000);
anim.setFillAfter(true);
this.startAnimation(anim);
}
Then tried to add this button to a view and it didn't work. Please help!
In your activity, use instead:
parentview.addView(myButton);
Then animate the button with this:
Animation animation = AnimationUtils.loadAnimation(getBaseContext(), R.anim.slide_right_in);
animation.setStartOffset(0);
myButton.startAnimation(animation);
This is an example of slide_right_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="800"/>
</set>
In addition,
This is a activity play animation function I wrote:
public Animation PlayAnim( int viewid, Context Con, int animationid, int StartOffset )
{
View v = findViewById(viewid);
if( v != null )
{
Animation animation = AnimationUtils.loadAnimation(Con, animationid );
animation.setStartOffset(StartOffset);
v.startAnimation(animation);
return animation;
}
return null;
}
You can call this like this:
PlayAnim(R.id.bottombar, (Context) this, R.anim.slide_right_in, 0);
Where:
1st parameter is the id of the view you want to apply the animation on.
2nd paramenter isThe context retrieved inside your activity.
3rd parameter is the desired animation that you put inside your anim resource folder or from android predefined animations.
4rd paremeter is the animation startoffset.
I tested your animated button implementation and it works correctly. There must be some other problem. Probably the way you add the button to the layout.
To add your button to the relative layout use code like this.
RelativeLayout rl = (RelativeLayout)findViewById(R.id.rl);
MyButton b1 = new MyButton(Main.this);
b1.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
rl.addView(b1);
Or you can inflate the button from layout. To do this create layout mybtn.xml containing your button implementation:
<?xml version="1.0" encoding="utf-8"?>
<PACKAGE_OF_MYBUTTON_HERE.MyButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
To add it to your layout call:
RelativeLayout rl = (RelativeLayout)findViewById(R.id.rl);
Button b = (Button)getLayoutInflater().inflate(R.layout.mybtn, rl, false);
rl.addView(b);
There might be a problem with proper positioning of your view when you add it to the relative layout. Just add code like this before calling rl.addView(b1) (the code snippet adds new button below someOtherView).
LayoutParams lp = new LayoutParams(b.getLayoutParams());
lp.addRule(RelativeLayout.BELOW, someOtherView.getId());
b.setLayoutParams(lp);
You can try adding this to your code just before adding view.I guess this code would work for any view changes. In my case was switching 2 views with animation.
TransitionManager.beginDelayedTransition(layoutlocation);//layoutlocation is parent layout(In my case relative layout) of the view which you gonna add.
Hope it works.Took 2 days for me to make this work.
It's not always necessary to use animation class to get actual animation. We can provide a delay when adding views to layout using handler as shown.
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
Animation fadeanimation = new AlphaAnimation(0,1);
fadeanimation.setDuration(position*60+100);
child.setAnimation(fadeanimation);
linearLayout.addView(child);
}
}, position*60);