Android Bottomsheet TextView height not adjusted on first time expanding - android

TL;DR: TextView in Bottomsheet not showing wrapped multi-line text the first time Bottomsheet is expanded, but adjust itself after it collapsed.
So I am using the Bottomsheet from design-23.2.1 library.
My layout file looks like this:
<android.support.design.widget.CoordinatorLayout>
......
<LinearLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="#dimen/bottom_sheet_peek_height"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
The content of the Bottomsheet is basically a list:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false" />
...
</LinearLayout>
The issue is whenever the Bottomsheet is set to STATE_EXPANDED the first time, the TextView is single line and text is wrapped, and there is no ellipsis … at line end.
Then after it's set to STATE_COLLAPSED, TextView's height is fine and multi-lined properly.
I know height re-layout happened after set to STATE_COLLAPSED because I slide it from collapse and the multi-line is already there.
A work around is provided here. I followed it and added this:
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.onLayoutChild(coordinatorLayout,
bottomSheetView,
ViewCompat.LAYOUT_DIRECTION_LTR);
}
}
........
}
It did actually make the height re-adjust when Bottomsheet is expanded the first time. However it occurred abruptly right after the expanding animation is done.
Is there any way to adjust the height ahead of the expanding animation just like how Google Map does?
Update
I found that this issue is because I have set Bottomsheet to STATE_COLLAPSED before it was expanded. If that was not set then the problem is gone and height is adjusted properly the first time.
Now my question is: why set it to STATE_COLLAPSED before expanding will cause that issue?

if for some reason you still have to use old support library, here is the workaround for this.
mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull final View bottomSheet, int newState) {
bottomSheet.post(new Runnable() {
#Override
public void run() {
//workaround for the bottomsheet bug
bottomSheet.requestLayout();
bottomSheet.invalidate();
}
});
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});

After switching to design library 24.0.0, the issue cannot be reproduced anymore.
Thanks for the efforts from the Android team to make our life easier and easier.

Related

RecyclerView startSmoothScroll() incorrect scroll amount with AppBarLayout?

I'm having trouble implementing smooth scroll with a RecyclerView when it is paired with AppBarLayout. This is my layout:
<CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#ff0000"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</AppBarLayout>
<RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</CoordinatorLayout>
I try to scroll to a position like so:
RecyclerView.SmoothScroller ss = new LinearSmoothScroller(getActivity()) {
#Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_END;
}
};
ss.setTargetPosition(position);
llm.startSmoothScroll(ss);
There are two problems:
If the target position is off the bottom edge of the recycler view, the scroll amount is incorrect - it is offset by the height of the AppBarLayout. If I change to SNAP_TO_START, then it works fine. Or, if I remove the AppBarLayout, it works fine in all cases.
Attempting to scroll to the last element in the recycler view is broken in additional ways. The SNAP_TO_START flag usually works fine (see #1), but in this case the recycler view refuses to scroll it up completely.
So removing the AppBarLayout fixes everything, is there some additional setting needed when using this with AppBarLayout? I'm on the latest support library version.
Thanks
Please use smoothScrollToPosition to fix your issue as below.
RecyclerView rv = (RecyclerView)findViewById(R.id.recyclerView);
rv.smoothScrollToPosition(mMessages.count-1);
the fist solution is
#Override
public int calculateDyToMakeVisible(View view, int snapPreference) {
return super.calculateDyToMakeVisible(view, snapPreference) - offset;
}
where offset may be
offset = getActionBarHeight(context);
public int getActionBarHeight(#NonNull Context context) {
final TypedArray ta = context.getTheme().obtainStyledAttributes(
new int[] {android.R.attr.actionBarSize});
int actionBarHeight = (int) ta.getDimension(0, 0);
return actionBarHeight;
}
the second solution is to replace app:layout_scrollFlags="scroll|enterAlways" with app:layout_scrollFlags="enterAlways" to prevent the actionBar from hiding

FloatingActionButton scroll aware issues with RecyclerView inside FrameLayout

One of my projects is using a common behavior class to hide/show the fab button which works perfect. Now, do to some layout requirement changes, the show fab on scroll up is not working.
The CoordinatorLayout setup is standard and it contains a ViewPager which loads Fragments in it. The changes to the Fragment layout has caused the fab show behavior to no longer work correctly.
Here is the original working Fragment layout:
<SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/filterMenu"
android:clipToPadding="false" />
</SwipeRefreshLayout>
Here is the new Fragment layout which doesn't work:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="#+id/emptyStateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:translationY="#dimen/home_empty_state_y_offset">
<ImageView
android:id="#+id/emptyStateImage"
android:layout_width="wrap_content"
android:layout_height="160dp"
android:layout_centerInParent="true"
android:src="#drawable/home_empty_state_animation" />
</RelativeLayout>
<SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/filterMenu"
android:clipToPadding="false" />
</SwipeRefreshLayout>
</FrameLayout>
It seems like the added FrameLayout is causing these issues but I'm not sure why. Is this a by design problem? or am I missing something?
Well solved the issue. There is some type of bug or something odd with the FloatingActionButton and CoordinatorLayout.
FloatingActionButton.hide() makes the button visibility GONE. This seems to cause the CoordinatorLayout to ignore further events for FloatingActionButton. That is the reason why scrolling down didn't show the button again.
The solution was to make sure the visibility of the FloatingActionButton was set to INVISIBLE after calling FloatingActionButton.hide().
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE)
{
// This fixes odd issue where fab doesn't show when scrolling down. Seems like the fab
// is being set as GONE when hidden. This causes the events on this view to be ignored
// by the CoordinatorLayout.
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onShown(FloatingActionButton fab) {
super.onShown(fab);
}
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
child.setVisibility(View.INVISIBLE);
}
});
}
else if (dyConsumed <= 0 && child.getVisibility() != View.VISIBLE)
{
child.show();
}
}
I had the same problem after update support library to version 25.1.0. If you set views visibility to GONE in your behavior class, now those views are ignored. So possible solutions are to downgrade support library or update your behavior class - make views INVISIBLE instead of GONE.

ActionBar is scrolled up when using adjustpan

I have been stuck into this for quite some time. I am trying to develop a chat module. I have been stuck into this part where when the SoftInputKeyboard overlays the content of the RecyclerView. I have tried almost every combination of adjustResize and adjustPan with stateHidden and stateVisible with no success at all.
On Using adjustPan the ActionBar gets hidden along with 2-3 recyclerview items.
I have attached screenshots. Any help with be appreciated.
XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/light_grey"
android:fitsSystemWindows="true"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
android:id="#+id/listFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<EditText
android:id="#+id/messageInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#android:color/white"
android:hint="Write a message"
android:inputType="textMultiLine"
android:padding="16dp"
android:textSize="14sp" />
<ImageView
android:id="#+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/colorPrimary"
android:contentDescription="#null"
android:padding="12dp"
android:src="#drawable/ic_send" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview_chat_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/listFooter"
android:layout_marginTop="8dp" />
</RelativeLayout>
A ) AdjustResize
B ) With AdjustPan
The android:windowSoftInputMode does not scroll your content for you when the keyboard is shown/hidden.
The documentation says:
adjustResize - The activity's main window is always resized to make room for the soft keyboard on screen.
adjustPan - The activity's main window is not resized to make room for the soft keyboard. Rather, the contents of the window are
automatically panned so that the current focus is never obscured by
the keyboard and users can always see what they are typing. This is
generally less desirable than resizing, because the user may need to
close the soft keyboard to get at and interact with obscured parts of
the window.
Basically this means that adjustResize makes your rootview smaller and puts the softKeyboard below it. And adjustPan pushes the top half of the rootview out of the screen to make room for the the softKeyboard.
I would suggest using adjustResize because it wont push your Toolbar out of the screen. Then you would have to scroll the content yourself. Its easier said than done obviously, but there are methods built in to do this.
First you would have to get the last visible item position in the recyclerview.
//class variable
private int lastVisiblePosition = 0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
//...
recyclerview_chat_main.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
super.onScrolled(recyclerView, dx, dy);
lastVisiblePosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();
}
});
//...
}
Then you have to do is scroll to that item when the SoftKeyboard is shown, the issue with that is there is no built in way to get when the keyboard is shown, fortunately someone has already addressed that here: https://stackoverflow.com/a/25681196/2027232
using Jaap's answer we could add something like this:
//class variables
private ViewGroup rootLayout = null;
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = null;
#Override
protected void onCreate(Bundle savedInstanceState)
{
//...
ViewGroup rootLayout = (ViewGroup)findViewById(android.R.id.content);
keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
int heightDiff = rootLayout.getRootView().getHeight() - rootLayout.getHeight();
int contentViewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getHeight();
if(heightDiff > contentViewTop)
{
recyclerview_chat_main.getLayoutManager().scrollToPosition(lastVisiblePosition);
}
}
};
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
//...
}
And lastly dont forget to remove the global listener when the activity gets destroyed:
#Override
protected void onDestroy()
{
super.onDestroy();
if(rootLayout != null && keyboardLayoutListener != null)
rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(keyboardLayoutListener);
}
The accepted answer didn't work for me.
This works:
In your activity tag in manifest, add android:windowSoftInputMode="adjustResize".
In onCreate method of your activity add this:
View.OnLayoutChangeListener layoutChangeListener = new View.OnLayoutChangeListener()
{
#Override
public void onLayoutChange(View v, int left, final int top, int right, final int bottom,
int oldLeft, final int oldTop, int oldRight, final int oldBottom)
{
if (oldBottom != 0)
{
//when softkeyboard opens, the height of layout get's small, and when softkeyboa
//-rd closes the height grows back(gets larger).We can find the change of height
// by doing oldBotton - bottom, and the result of subtraction is how much we nee
//-d to scroll. Change of height is positive if keyboard is opened, and negative
//if it's closed.
int pixelsToScrollVertically = oldBottom - bottom;
recyclerView.scrollBy(0, pixelsToScrollVertically);
}
}
};
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
recyclerView.addOnLayoutChangeListener(layoutChangeListener);

Try to Understand the behavior of BottomSheet in android support library 23.2.1

I am trying to implement Bottom sheet in one of my activities and I am kind of confused by the way it is behaving!
So here is the problem, I have an activity in which I am trying to show Bottom sheet and I see that:
if we dont set the app:behavior_peekHeight property then the Bottom sheet never works
If you set the PeekHeight to something less than 30dp (basically just to hide it from screen)
If you set app:behavior_peekHeight to more than 30dp in layout file and try to set the state of bottomSheetBehavior to STATE_HIDDEN in you onCreate method your app crashes with this error
caused by:
java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.Object java.lang.ref.WeakReference.get()' on a null object reference at android.support.design.widget.BottomSheetBehavior.setState(BottomSheetBehavior.jav a:440)
at myapp.activity.SomeActivity.onCreate(SomeActivity.java:75)
I am really confused on why is it not allowing me to hide it in onCreate? or why cant we just set the peekHeight to 0 so that it is not visible on screen unless we call the STATE_EXPANDED or even not setting that property should default it to hide! or atleast I should be able to set it as hidden in my onCreate!
am I missing something? or is the behavior of the BottomSheet rigid?
my layout file for the BottomSheet is something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="#android:color/white"
android:layout_height="100dp"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="40dp" <!-- I cant set this less than 30dp just to hide-->
app:layout_behavior="#string/bottom_sheet_behavior"
tools:context="someActivity"
android:id="#+id/addressbottomSheet"
tools:showIn="#layout/some_activity">
in my activity I am doing something like this:
#InjectView(R.id.addressbottomSheet)
View bottomSheetView;
#Override
protected void onCreate(Bundle savedInstanceState) {
....
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
// only if I have set peek_height to more than 30dp
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
In my onclick I am doing this:
#Override
public void onItemClick(View view, int position) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
After working on this issue for few more days I found one alternate solution for this:
Instead of using the Bottom_sheet directly inside your layout, if we create a Bottom_Sheet fragment and then instantiate it in the activity this issue will not occur and the bottom sheet will be hidden and we dont need to specify the peek_height
here is what I did
public class BottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_bottom_sheet, container, false);
}
Then in my activity
bottomSheetDialog = BottomSheetDialog.newInstance(addressList.get(position), position);
bottomSheetDialog.show(getSupportFragmentManager(), AddressActivity.class.getSimpleName());
This actually solved my problem of bottom sheet being not hidden when the activity starts but I am still not able to understand why if bottom_sheet is included directly we face that problem!
(Referring to the question) Suzzi bro the issue with your code is you are trying to call the setState method directly inside onCreate. This is will throw a nullPointer because the WeakReference is not initialized yet. It will get initialized when the Coordinator layout is about to lay its child view.
onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
Called when the parent CoordinatorLayout is about the lay out the
given child view.
So the best approach is set the peek height to 0 and show/hide inside the onItemClick listener.
Here is my code:
bottom_sheet.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Bottom sheet"
android:textColor="#android:color/black" />
</LinearLayout>
activity_main.xml
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show hide bottom sheet" />
<include
android:id="#+id/gmail_bottom_sheet"
layout="#layout/bottom_sheet" />
MainActivity.java
public class MainActivity extends AppCompatActivity {
boolean isExpanded;
Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.gmail_coordinator);
final View bottomSheet = coordinatorLayout.findViewById(R.id.gmail_bottom_sheet);
final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isExpanded) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
isExpanded = !isExpanded;
}
});
}
}
Here initially the bottom sheet is not visible. On clicking the button we will be set the state to STATE_COLLAPSED/STATE_EXPANDED.
The tutorial I followed to make this demo app is listed below:
Bottom Sheet with Android Design Support Library
The reason its crashing is due to the fact that the weak reference is not being set until one of the last lines in onLayoutChild, which gives you your null ptr exception.
What you can do is create a custom BottomSheet Behavior and override onLayoutChild, setting the expanded state there.
An example can be found here:
NullPointerExeption with AppCompat BottomSheets
To avoid the Null pointer exception, set the state to HIDDEN like this in onCreate()
View bottomSheetView = findViewById(R.id.bottomsheet_review_detail_id);
mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
bottomSheetView.post(new Runnable() {
#Override
public void run() {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
});

Use of setVisibility(View.INVISIBLE) with pre Lollipop Circular Reveal library

I'm working on a project in which I want to use the Circular Reveal effect as per the Material Design. Project has minSDK = 11, so for compatibility with pre-Lollipop devices, I'm using this library https://github.com/ozodrukh/CircularReveal
I've a fragment with a FloatingActionButton that, when tapped, will transform itself in the CardView, like described here FAB transformations.
Once the card is revealed it has a button to revert the animation, re-transforming the card into the FAB. Now my problem is this: let's say that a user tap the FAB and the CardView is revealed. Now the user rotate his device, so the activity resets the fragment. What I want to achieve is the card stay visible and revealed, while the FAB should be disabled and invisible. The problem is that if I simply use setVisibility(View.INVISIBLE) on my FAB it doesn't work (note that if I use getVisibility() on it just after setting it invisible, it correctly returns me the value 4 == View.INVISIBLE, but the fab is still visible). I've to wrap the setVisibility(...) call inside a postDelayed() with at least 50-100 ms of delay to make the fab invisible.
So my question is: am I doing things right or there's a better way to accomplish what I want (because it seems very ugly to me)?
Here's some code. This is my XML layout of the fragment:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/my_appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/toolbar_expanded_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="70dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ToolbarPopupTheme"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
...
<io.codetail.widget.RevealFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/my_fragment" />
</io.codetail.widget.RevealFrameLayout>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
app:fabSize="mini"
app:layout_anchor="#+id/my_appbar"
app:layout_anchorGravity="bottom|left|start" />
</android.support.design.widget.CoordinatorLayout>
This is the XML layout of the included CardView:
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="center"
card_view:cardCornerRadius="2dp"
android:visibility="invisible"
android:focusableInTouchMode="true">
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent">
...
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Ok" />
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel" />
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
And this is the code of my Fragment:
public class MyFragment extends Fragment {
private static final String CARD_OPEN_TAG = "CARD_OPEN_TAG";
public static MyFragment newInstance(){
return new MyFragment();
}
private int cardOpen;
private FloatingActionButton fabAddPublication;
private CardView card;
private Button cardCancel;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_magazines, container, false);
toolbar = (Toolbar) rootView.findViewById(R.id.layout);
...
// Initialize view status
if (savedInstanceState != null){
cardOpen = savedInstanceState.getInt(CARD_OPEN_TAG);
} else {
cardOpen = -1;
}
...
// Get FAB reference
fab = (FloatingActionButton) rootView.findViewById(R.id.fab_id);
// Get card reference
card = (CardView) rootView.findViewById(R.id.card_id);
editorPublication.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Using this event because I need my card to be measured to move correctly fab at his center
if (cardOpen != -1){
// Move FAB to center of card
fab.setTranslationX(coordX); // WORKS
fab.setTranslationY(coordY); // WORKS
// fab.setVisibility(View.INVISIBLE) -> DOESN'T WORK, fab remain visible on top and at center of my card
// Ugly workaround
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Hide FAB
fab.setVisibility(View.INVISIBLE);
}
}, 50); // Sometimes fails: if device/emulator use too much time to "rotate" screen, fab stay visible
// Remove listener
ViewTreeObserver obs = card.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
obs.removeOnGlobalLayoutListener(this);
else obs.removeGlobalOnLayoutListener(this);
}
}
});
if (editorOpen != -1){
fab.setEnabled(false); // WORKS
card.setVisibility(View.VISIBLE); // WORKS
}
// Get editors buttons reference
cardCancel = (Button) card.findViewById(R.id.card_cancel_id);
// Set FAB listener
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Explode FAB
explodeFab(fab, card); // This method trigger the reveal animation
cardOpen = card.getId();
}
});
// Set editors button listeners
cardCancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Implode FAB
implodeFAB(fab, card); // This card reverts the reveal animation
cardOpen = -1;
}
});
...
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
...
outState.putInt(CARD_OPEN_TAG, cardOpen);
}
}
I don't think that what you're doing is correct and here's why:
1.
That circular reveal library you're using is incompatible with hardware acceleration on Android 11 to 17 (3.0 to 4.2). It's using Canvas.clipPath() - a method which was implemented in software only up to Android 4.3. It means that you have to turn hardware acceleration off or your app will crash on not supported OpenGL call.
The best way to clip layouts is to use Canvas.saveLayer()/restoreLayer(). It's the only way to support all devices, get antialiased image and support invalidate/layout/draw flow correctly.
2.
Relying on timers and layout listeners to change layouts means that something is not really working for you. Each change can be executed directly without waiting. You just have to find the right place for that piece of code.
Timers can also trigger when your app is in background. It means that it will crash while trying to access UI from Timer thread.
Maybe you're calling setVisibility(true) somewhere in the code and that's why your FAB is visible? Setting visibility is ok and should work without any delayed calls. Just save visibility as FAB's state and restore it after orientation change.
If you wish to position the FAB at the center of the toolbar, wrap them both in a FrameLayout (wrap_content) and position the FAB with layout_gravity="center". That should allow you to remove layout listener.
3.
support.CardView is broken and shouldn't be used at all. It looks and works a little bit different on Lollipop and on older systems. The shadow is different, the padding is different, content clipping doesn't work on pre-Lollipop devices, etc. That's why it's hard to get consistent, good results on all platforms.
You should consider using plain layouts for that purpose.
4.
Some animations are cool-looking, but hard to implement, doesn't give any value and quickly become irritating, because the user has to wait for the animation to finish each time he/she clicks the button.
I'm not saying no for your case, but you may consider removing the transformation and using a drop down menu, a bottom sheet, a static toolbar or a dialog for that purpose.

Categories

Resources