I have an special use case where I have an an custom FrameLayout which contains 2 custom views. I will call it MyWrapperFL.
I have another custom view, which is outside this custom FrameLayout.
All of this is wrapped inside an basic FrameLayout.
So I have the following:
CustomView 1
MyWrapperFL
CustomView 2
CustomView 3
Now I want to change the order of the views like this:
CustomView 3 (top most)
CustomView 1
CustomView 2 (bottom most)
I've tried using the following utility method I've created:
/**
* Reorder the position of the views on their Z axis.
* <br/>
* The order of the views is important. The first in the list will be the top most view while the last in the list
* will be the bottom most view.
*
* #param views
* The list of views which to reorder. Can be {#code null}.
*/
private void updateViewsOrder(#Nullable View... views) {
if (null == views) {
// No views specified for reorder - exit early
return;
}
List<View> viewList = Arrays.asList(views);
int translationZ = viewList.size();
for (View view : viewList) {
if (null != view) {
view.bringToFront();
view.getParent().requestLayout();
view.invalidate();
}
}
}
But result is not the desired one. Instead of the expected result I get the following:
CustomView 1 (top most)
CustomView 3
CustomView 2 (bottom most)
I'm guessing this is because they have different parents. Can anyone help me with this? Is there a way to do it?
Related
I have built a custom view, where when the user taps on the view it expands. Before it expands I call getTop() to get the current Y coordinate, however it returns zero always. Im calling getTop() from within the custom view class. Is calling getTop() from the class that implements the view not allowed?
public void expand(float targetHeight) { //This custom view extends FrameLayout
int top = getTop(); // returns zero
rootLayout.setLayoutParams(new FrameLayout.LayoutParams(widthPixels, dpToPx((int) targetHeight)));
webView.setLayoutParams(new FrameLayout.LayoutParams(widthPixels, dpToPx((int) targetHeight)));
}
The method getTop() returns the Top position of the view relative to its parent. So maybe is in the position 0 of his parent.
Can you please share your XML so I can have a better understanding of the layout?
I have a RecyclerView of items and a layoutManager that is of type StaggeredGridLayoutManager. I was in an interesting situation, I wanted my items staggered to look like this:
but my views are all the same size, so they would not stagger. To correct the problem I needed to add an offset at the start of the 2nd column. Since I was also creating my own custom decorator class I figured the best way to accomplish this was to just add an offset for the first right column item in my list using the getItemsOffsets method.
Here is the relevant code for my decorator class:
public class StampListDecoration extends RecyclerView.ItemDecoration {
...
#Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// good example here: https://stackoverflow.com/questions/29666598/android-recyclerview-finding-out-first-and-last-view-on-itemdecoration/30404499#30404499
/**
* Special case. Te first right side item in the list should have an extra 50% top
* offset so that these equal sized views are perfectly staggered.
*/
if (parent.getChildAdapterPosition(view) == 1) {
/**
* We would normally do a outRect.top = view.getHeight()/2 to create a 50% top offset on the first right item in the list.
* However, problems would arise if we paused the app when the top right item was scrolled off screen.
* In this situation, when we re-inflated the recyclerview since the view was off screen
* Android would say the height of the view was zero. So instead I added code that
* looked for the height of the top most view that was visible (and would therefore
* have a height.
*
* see https://stackoverflow.com/questions/29463560/findfirstvisibleitempositions-doesnt-work-for-recycleview-android
* because as a staggeredGrid layout you have a special case first visible method
* findFirstVisibleItemPositions that returns an array of (notice the S on the end of
* the method name.
*/
StaggeredGridLayoutManager layoutMngr = ((StaggeredGridLayoutManager) parent.getLayoutManager());
int firstVisibleItemPosition = layoutMngr.findFirstVisibleItemPositions(null)[0];
int topPos = 0;
try {
topPos = parent.getChildAt(firstVisibleItemPosition).getMeasuredHeight()/2;
} catch (Exception e) {
e.printStackTrace();
}
outRect.set(0, topPos, 0, 0);
} else {
outRect.set(0, 0, 0, 0);
}
}
}
my problem is that these offsets are not getting saved to state when my activity pauses/resumes. So when I switch to another app and switch back, the right column in my RecyclerView slides back to the top...and I lose my stagger.
Can someone show me how to save my offset state? Where are offsets supposed to be saved? I was assuming the LayoutManager would save this information, and I'm saving the LayoutManager state, but that does not seem to be working.
I'm building an app with 2 fragments (with ViewPager). Each fragment consists of 4 buttons. I use also onTouch listener with these buttons. I use getLocationOnScreen(...) method to get the view's (button) position on screen. But as I slide from first fragment to the second one, the location of the next 4 buttons is relative to the first fragment. I mean, the x coordinate of these buttons is taking into consideration the previous fragment width.
My question is - how do I get the second fragment's buttons position relative to this fragment page and not the previous one?
Thanks!
EDIT : Added a code snippet:
Here is how i keep my buttons position on screen into a hash-map collection (this is done for each of the two fragments i have):
public void getViewsPosition(final ArrayList<View> viewArr) {
for (final View v : viewArr) {
final ViewTreeObserver vto = v.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int[] i = new int[2];
v.getLocationOnScreen(i);
_rect = new Rect(i[0], i[1], i[0] + v.getWidth(), i[1]
+ v.getHeight());
view_to_rect.put(v, _rect);
if (Build.VERSION.SDK_INT < 16) {
vto.removeGlobalOnLayoutListener(this);
} else {
vto.removeOnGlobalLayoutListener(this);
}
}
});
}
}
This keeps the correct coordinates for the first fragment's buttons. But for the second fragment, the x-coordinate is relative to the first fragment. So I get the button's x coordinate within this fragment PLUS the first fragment width. The y-coordinate is, of course, correct within all fragment.
How can I recreate the layout animation affects you see in the Gmail app on Android 3.0+
I know you can use PropertyAnimators, ObjectAnimators, etc., but in my scenario, I have a several fragments on the screen which can be interchanged, so they're all contained within their own FrameLayouts, and all the FrameLayouts are in a RelativeLayout.
I can't figure out how to change the width of the FrameLayouts, since you have to edit that through the LayoutParams object.
Any ideas?
My take on this can be viewed on GitHub here
It demonstrates one way of hiding a fragment with an animation, like Gmail does.
The animation isn't perfect as the left fragment is moving slightly faster than the right fragment is.
The method that does the hiding defines the animation in code and looks as follows:
/**
* Besides showing/hiding the left fragment, this method specifies that a
* layout animation should be used. It is defined as a sliding animation.
* The right fragment will use the default animation, which is a sliding
* animation also.
*
* If the left fragment's animation is removed from this method, the default
* animation will be used which is a fading animation.
*
* Please note that this method will only have an effect in those screen
* configurations where the list is hideable; by default, a width between
* 600 and 1024 dip which corresponds to a portrait view on tablets. Change
* the boolean value in layout_constants.xml to allow for it in other screen
* sizes.
*
* #param visible
*/
protected void setLeftFragmentVisible(boolean visible) {
if (leftFragment != null && (leftFragment.isVisible() || visible)
&& getResources().getBoolean(R.bool.leftHideable)) {
final float listWidth = getLeftFragment().getView().getWidth();
ViewGroup container = (ViewGroup) findViewById(R.id.dual_layout);
// Don't clip the children, we want to draw the entire fragment even
// if it is partially off-screen.
container.setClipChildren(false);
final LayoutTransition trans = container.getLayoutTransition();
/**
* This specifies the delay before the leftFragment will appear.
* Change if you want the right fragment to move before.
*/
trans.setStartDelay(LayoutTransition.APPEARING, 0);
/**
* This is the delay before the right fragment will start to occupy
* the space left by the left fragment
*/
trans.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
/**
* Adding, specifies that the left fragment should animate by
* sliding into view.
*/
ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "x",
-listWidth, 0f).setDuration(
trans.getDuration(LayoutTransition.CHANGE_APPEARING));
trans.setAnimator(LayoutTransition.APPEARING, animIn);
/**
* Removing, specifies that the left fragment should animate by
* sliding out of view.
*/
ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "x", 0f,
-listWidth).setDuration(
trans.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
trans.setAnimator(LayoutTransition.DISAPPEARING, animOut);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
if (getLeftFragment().isVisible()) {
fragmentTransaction.hide(getLeftFragment());
} else {
fragmentTransaction.show(getLeftFragment());
}
// The hiding/showing will automatically initiate the animations
// since
// we have specified that we want layout animations in the layout
// xml
fragmentTransaction.commit();
/*
* Display home as up to be able to view the list
*/
getActionBar().setDisplayHomeAsUpEnabled(!visible);
}
}
To make this work, you need to define that you want layout transition to be animated. One line in the top of your layout's xml structure will do that:
android:animateLayoutChanges="true"
If you don't want custom animations, you can remove everything before FragmentManager fragmentManager = getFragmentManager().
Now in my example I use fragments but the same principle should apply to any view.
Edit: Instead of changing the widths of your views manually, you should use layout weights instead to allow automatic resizing.
I have an activity with a couple of fragments inside. I'm using Frame Layout for each of the fragment and have Relative Layout as parent.
I've been having headaches on getting the exact location of a fragment in parent. But seems like I am only able to relative values.
So, is there anyway to get an exact location of a fragment within an activity?
I haven't done this with Fragments but with the contents of fragments.
If you use findById() on the activity that will find the id of anything inflated in that activity (including fragment contents etc).
You can then use something like View.getLocationOnScreen(int[]) this should give you the x and y of the View on screen. Never tried this on a fragment, but if you fragment is wrapped by a Layout then should be no problem.
Hope this helps?
Actually this might help, might need changing to take in two views as this method is inside a custom RelativeLayout I made:
/**
* Will return the X,Y of the Anchor view relative to this relative layout.. So It doesn't
* matter if the anchor view is nested, or any any other layout, this WILL find its location
* relative too this {#link HelpRelativeLayout}.
*
* #author chris.jenkins
* #param anchor
* #return
*/
private int[] calculateAnchorViewPosition(View anchor)
{
int[] loc = new int[2];
int[] conLoc = new int[2];
// getLocationInWindow(conLoc);
getLocationOnScreen(conLoc);
// anchor.getLocationInWindow(loc);
anchor.getLocationOnScreen(loc);
// get correct top left pos + half the h/w of the anchor
loc[0] = (loc[0] - conLoc[0]);
loc[1] = (loc[1] - conLoc[1]);
MyLog.d(loc[0] + " " + loc[1]);
return loc;
}