I'm trying to make Navigation Drawer which moves whole screen (whole height, not width). I'm not using ActionBar and other libraries, just default Android drawer. Anyone have any examples?
What you need to do is to set the translationX property of your content view by the pixel amount the drawer has moved to.
In your implementation of http://developer.android.com/reference/android/support/v4/widget/DrawerLayout.DrawerListener.html you should implement onDrawerSlide(View drawerView, float slideOffset).
In this method you should add this line.
mDrawerLayout.findViewById(R.id.your_content_id).setTranslationX(drawerView.getWidth() * slideOffset);
That should do the trick.
Implements DrawerListener (passing as parameter the R.id of your layout):
import android.animation.ObjectAnimator;
import android.support.v4.widget.DrawerLayout.DrawerListener;
import android.view.View;
public class DrawerLayoutListener implements DrawerListener {
private View _contentDrawer;
private int _idView;
public DrawerLayoutListener(int idView) {
_idView = idView;
}
#Override
public void onDrawerClosed(View arg0) {}
#Override
public void onDrawerOpened(View arg0) {}
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
if (_contentDrawer == null) _contentDrawer = ((View) drawerView.getParent()).findViewById(_idView);
float moveFactor = (drawerView.getWidth() * slideOffset);
ObjectAnimator.ofFloat(_contentDrawer, "translationX", moveFactor).setDuration(0).start();
}
#Override
public void onDrawerStateChanged(int arg0) {}
}
And set it as listener to your DrawerLayout:
drawer_layout.setDrawerListener(new DrawerLayoutListener(R.id.content_frame));
Related
My question is the same as this question (which is not a duplicate of this question).
The only answer to that question does not work for me as, rather than changing the default hamburger icon to the left of the activity's title, it just adds an additional hamburger icon to the right of my activity's title.
So how do I actually get this:
I've been poking around at it all day, but have got nowhere.
I see that Toolbar has a setNavigationIcon(Drawable drawable) method. Ideally, I would like to use a layout (that contains the hamburger icon and the badge view) instead of a Drawable, but I'm not sure if/how this is achievable - or if there is a better way?
NB - This isn't a question about how to create the badge view. I have already created that and have implemented it on the nav menu items themselves. So I am now just needing to add a similar badge view to the default hamburger icon.
Since version 24.2.0 of the support library, the v7 version of ActionBarDrawerToggle has offered the setDrawerArrowDrawable() method as a means to customize the toggle icon. DrawerArrowDrawable is the class that provides that default icon, and it can be subclassed to alter it as needed.
As an example, the BadgeDrawerArrowDrawable class overrides the draw() method to add a basic red and white badge after the superclass draws itself. This allows the hamburger-arrow animation to be preserved underneath.
import android.content.Context;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.v7.graphics.drawable.DrawerArrowDrawable;
import java.util.Objects;
public class BadgeDrawerArrowDrawable extends DrawerArrowDrawable {
// Fraction of the drawable's intrinsic size we want the badge to be.
private static final float SIZE_FACTOR = .3f;
private static final float HALF_SIZE_FACTOR = SIZE_FACTOR / 2;
private Paint backgroundPaint;
private Paint textPaint;
private String text;
private boolean enabled = true;
public BadgeDrawerArrowDrawable(Context context) {
super(context);
backgroundPaint = new Paint();
backgroundPaint.setColor(Color.RED);
backgroundPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(SIZE_FACTOR * getIntrinsicHeight());
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (!enabled) {
return;
}
final Rect bounds = getBounds();
final float x = (1 - HALF_SIZE_FACTOR) * bounds.width();
final float y = HALF_SIZE_FACTOR * bounds.height();
canvas.drawCircle(x, y, SIZE_FACTOR * bounds.width(), backgroundPaint);
if (text == null || text.length() == 0) {
return;
}
final Rect textBounds = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textBounds);
canvas.drawText(text, x, y + textBounds.height() / 2, textPaint);
}
public void setEnabled(boolean enabled) {
if (this.enabled != enabled) {
this.enabled = enabled;
invalidateSelf();
}
}
public boolean isEnabled() {
return enabled;
}
public void setText(String text) {
if (!Objects.equals(this.text, text)) {
this.text = text;
invalidateSelf();
}
}
public String getText() {
return text;
}
public void setBackgroundColor(int color) {
if (backgroundPaint.getColor() != color) {
backgroundPaint.setColor(color);
invalidateSelf();
}
}
public int getBackgroundColor() {
return backgroundPaint.getColor();
}
public void setTextColor(int color) {
if (textPaint.getColor() != color) {
textPaint.setColor(color);
invalidateSelf();
}
}
public int getTextColor() {
return textPaint.getColor();
}
}
An instance of this can be set on the toggle any time after it's instantiated, and the badge's properties set directly on the drawable as needed.
As the OP noted below, the Context used for the custom DrawerArrowDrawable should be obtained with ActionBar#getThemedContext() or Toolbar#getContext() to ensure the correct style values are used. For example:
private ActionBarDrawerToggle toggle;
private BadgeDrawerArrowDrawable badgeDrawable;
...
toggle = new ActionBarDrawerToggle(this, ...);
badgeDrawable = new BadgeDrawerArrowDrawable(getSupportActionBar().getThemedContext());
toggle.setDrawerArrowDrawable(badgeDrawable);
badgeDrawable.setText("1");
...
To simplify things a bit, it might be preferable to subclass ActionBarDrawerToggle as well, and handle everything through the toggle instance.
import android.app.Activity;
import android.content.Context;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class BadgeDrawerToggle extends ActionBarDrawerToggle {
private BadgeDrawerArrowDrawable badgeDrawable;
public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,
int openDrawerContentDescRes,
int closeDrawerContentDescRes) {
super(activity, drawerLayout, openDrawerContentDescRes,
closeDrawerContentDescRes);
init(activity);
}
public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,
Toolbar toolbar, int openDrawerContentDescRes,
int closeDrawerContentDescRes) {
super(activity, drawerLayout, toolbar, openDrawerContentDescRes,
closeDrawerContentDescRes);
init(activity);
}
private void init(Activity activity) {
Context c = getThemedContext();
if (c == null) {
c = activity;
}
badgeDrawable = new BadgeDrawerArrowDrawable(c);
setDrawerArrowDrawable(badgeDrawable);
}
public void setBadgeEnabled(boolean enabled) {
badgeDrawable.setEnabled(enabled);
}
public boolean isBadgeEnabled() {
return badgeDrawable.isEnabled();
}
public void setBadgeText(String text) {
badgeDrawable.setText(text);
}
public String getBadgeText() {
return badgeDrawable.getText();
}
public void setBadgeColor(int color) {
badgeDrawable.setBackgroundColor(color);
}
public int getBadgeColor() {
return badgeDrawable.getBackgroundColor();
}
public void setBadgeTextColor(int color) {
badgeDrawable.setTextColor(color);
}
public int getBadgeTextColor() {
return badgeDrawable.getTextColor();
}
private Context getThemedContext() {
// Don't freak about the reflection. ActionBarDrawerToggle
// itself is already using reflection internally.
try {
Field mActivityImplField = ActionBarDrawerToggle.class
.getDeclaredField("mActivityImpl");
mActivityImplField.setAccessible(true);
Object mActivityImpl = mActivityImplField.get(this);
Method getActionBarThemedContextMethod = mActivityImpl.getClass()
.getDeclaredMethod("getActionBarThemedContext");
return (Context) getActionBarThemedContextMethod.invoke(mActivityImpl);
}
catch (Exception e) {
return null;
}
}
}
With this, the custom badge drawable will be set automatically, and everything toggle-related can be managed through a single object.
BadgeDrawerToggle is a drop-in replacement for ActionBarDrawerToggle, and its constructors are exactly the same.
private BadgeDrawerToggle badgeToggle;
...
badgeToggle = new BadgeDrawerToggle(this, ...);
badgeToggle.setBadgeText("1");
...
I am working on morphing a floating action button (FAB) to a toolbar and things work smoothly and perfectly with the following code:
layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context="sg.com.saurabh.designlibraryexpirements.ToolbarMorphActivity">
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_marginRight="#dimen/activity_vertical_margin"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:src="#drawable/ic_add" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
style="#style/ToolBarTheme"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="top"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:visibility="invisible"
tools:visibility="visible" />
</FrameLayout>
activity:
package sg.com.saurabh.designlibraryexpirements;
import android.animation.Animator;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
public class ToolbarMorphActivity extends AppCompatActivity {
Toolbar toolbar;
FloatingActionButton fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar_morph);
toolbar = (Toolbar) findViewById(R.id.toolbar);
fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(mFabClickListener);
}
private View.OnClickListener mFabClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
fab.animate()
.rotationBy(45)
.setInterpolator(new AnticipateOvershootInterpolator())
.setDuration(250)
.start();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
fab.setVisibility(View.GONE);
}
},50);
revealToolbar();
}
};
private void revealToolbar() {
toolbar.setVisibility(View.VISIBLE);
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
Animator animator = ViewAnimationUtils.createCircularReveal(toolbar, x, y, 0, toolbar.getWidth())
.setDuration(400);
animator.setInterpolator(new FastOutLinearInInterpolator());
animator.start();
}
private void dismissToolbar() {
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
Animator animator = ViewAnimationUtils.createCircularReveal(toolbar, x, y, toolbar.getWidth(), 0)
.setDuration(400);
animator.setInterpolator(new LinearOutSlowInInterpolator());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
toolbar.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
fab.setVisibility(View.VISIBLE);
fab.animate()
.rotationBy(-45)
.setInterpolator(new AccelerateInterpolator())
.setDuration(100)
.start();
}
},200);
}
#Override
public void onBackPressed() {
if(toolbar.getVisibility() == View.VISIBLE) {
dismissToolbar();
}
else
super.onBackPressed();
}
}
The circular reveal works as expected for the above layout. However thing break up when I change the layout_gravity of the fab and toolbar to bottom instead of top. The rotate animation works and then the toolbar just appears without the circular reveal animation. I am completely stumped by how that breaks the circular reveal animation.
The fix for you would be to replace:
private void revealToolbar() {
....
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
....
}
by
private void revealToolbar() {
...
int x = (int)fab.getX() + fab.getWidth()/2;
int y = fab.getHeight()/2;
...
}
The reason is that createCircularReveal is taking parameters centerY and centerX as coordinates of the center of the animating circle, relative to view (i.e. Toolbar, in our case).
See method ViewAnimationUtils.createCircularReveal definition:
........
* #param view The View will be clipped to the animating circle.
* #param centerX The x coordinate of the center of the animating circle, relative to
* <code>view</code>.
* #param centerY The y coordinate of the center of the animating circle, relative to
* <code>view</code>.
* #param startRadius The starting radius of the animating circle.
* #param endRadius The ending radius of the animating circle.
*/
I have an app where I override the onDrawerClosed of an ActionBarDrawerToggle. I have noticed that there is always an unwanted delay before the onDrawerClosed method is called. The problem is very easy to spot: when I override onDrawerSlide and put in some logging I see clearly that the slideOffset value is 0.0 somewhere between 50-100ms before onDrawerClosed is called. Why is this delay there?
mDrawerToggle = new ActionBarDrawerToggle([...]) {
public void onDrawerClosed(View view) {
Log.d(TAG, String.format("% 12d onDrawerClosed", Calendar.getInstance().getTimeInMillis()));
super.onDrawerClosed(view);
}
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
}
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
Log.d(TAG, String.format("% 12d onDrawerSlide %f", Calendar.getInstance().getTimeInMillis(), slideOffset)));
// THIS IS CALLED WITH slideOffset = 0.0f TOO LONG BEFORE onDrawerClosed
super.onDrawerSlide(drawerView, slideOffset);
}
};
i'm not sure about this answer can you put that Log.d after super invoked
I have a SurfaceView that should be set with setZOrderOnTop(true) , is it possible to have a DrawerLayout that opens over it. currently the surface draws over the drawer.
In a similar situation the solution from DrawerLayout not working with Android 4.4 & SurfaceView worked for me:
#Override
public void onDrawerSlide(View drawerView, float slideOffset)
{
mDrawerLayout.bringChildToFront(drawerView);
mDrawerLayout.requestLayout();
}
Inside of your DrawerListener
The below code worked for me.
actionBarDrawerToggle = new ActionBarDrawerToggle(this,drawerLayout,R.string.open, R.string.close)
{
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
drawerLayout.bringChildToFront(drawerView);
drawerLayout.requestLayout();
}
};
You must have used the facebook android app. I want to implement the same navigation that the facebook is using like:
on swipping left the menu is opened and swipping right the chat list is shown. And in the middle the activities and layouts keep on changing. But I am confused how to make such a navigation. (Most noticable thing is that the middle page is half shown when the swipping is done on left or right.) help?
You have two good options.
Navigation Drawer: http://developer.android.com/design/patterns/navigation-drawer.html
Sliding Drawer: https://github.com/jfeinstein10/SlidingMenu
You can make multiple navigation drawers for your use case.
I installed the FB app an hour ago. When you tap the icon in the top-right corner of the main content, the main content moves left to reveal content on the right. The revealed content remains fixed and behind the main content during the move.
I wrote this lightweight abstraction based on the above analysis:
package org.yourdomain.app;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
abstract public class NavigatorActivity extends Activity {
static public final String TAG = "NavigatorActivity";
static public final Boolean DEBUG_LIFECYCLE = false;
/**
* True when menu is on and vice versa
*/
private boolean mToggled;
/**
* Percentage of the main content to keep shown
*/
private float mDistance;
/**
* Navigator listener
*/
private NavigatorListener mListener;
/**
* Content view
*/
private ViewGroup mContentLayout;
/**
* Menu frame
*/
private FrameLayout mMenuLayout;
/**
* Main frame
*/
private FrameLayout mMainLayout;
/**
* Speed of toggle animation
*/
private long mSpeed;
/**
* Width of the content view
*/
private int mContentWidth;
/**
* Height of the content view
*/
private int mContentHeight;
/**
* The current distance to slide the main frame
*/
private int mToggleDistance;
/**
*
*/
public NavigatorActivity() {
mToggled = false;
mDistance = 80;
mSpeed = 300l;
}
/**
* Speed setter.
*
* Controls how fast the menu frame is revealed.
*
* #param speed
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
/**
* Distance setter.
*
* The distance is the % of the oriented screen to pull the main frame in order to reveal the
* menu frame.
*
* #param distance
*/
public void setDistance(float distance) {
mDistance = distance;
}
/**
* Navigator listener setter.
*
* #param listener
*/
public void setNavigatorListener(NavigatorListener listener) {
mListener = listener;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG_LIFECYCLE) Log.v(TAG, "onCreate " + this + ": " + savedInstanceState);
initContentLayout();
setContentView(mContentLayout);
initMenuLayout();
mContentLayout.addView(mMenuLayout);
initMainLayout();
mContentLayout.addView(mMainLayout);
}
/**
* Initializes the main frame.
*/
private void initMainLayout() {
int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mMainLayout = new FrameLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mContentWidth, mContentHeight);
}
};
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(hw, hw);
p.addRule(RelativeLayout.ALIGN_PARENT_TOP);
p.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
mMainLayout.setLayoutParams(p);
}
/**
* Initializes the menu frame.
*/
private void initMenuLayout() {
int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mMenuLayout = new FrameLayout(this);
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(hw, hw);
p.addRule(RelativeLayout.ALIGN_PARENT_TOP);
p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mMenuLayout.setLayoutParams(p);
}
/**
* Initialize the activity's content layout.
*/
private void initContentLayout() {
final int hw = RelativeLayout.LayoutParams.MATCH_PARENT;
mContentLayout = new RelativeLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mContentWidth = MeasureSpec.getSize(widthMeasureSpec);
mContentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mContentWidth, mContentHeight);
mToggleDistance = Math.round((mDistance / 100f) * (float) mContentWidth);
}
};
mContentLayout.setLayoutParams(new RelativeLayout.LayoutParams(hw, hw));
}
/**
* Inflates two project XML layout and adds them to the menu and main layout frames.
*
* #param menuLayoutResId
* #param mainLayoutResId
*/
protected void setContentViews(int menuLayoutResId, int mainLayoutResId) {
final LayoutInflater inflater = getLayoutInflater();
inflater.inflate(menuLayoutResId, mMenuLayout);
inflater.inflate(mainLayoutResId, mMainLayout);
}
/**
* Toggle the menu frame.
*/
final public void toggleNavigator() {
final boolean isToggled = mToggled = !mToggled;
if (DEBUG_LIFECYCLE) Log.v(TAG, "toggleNavigatorMenu " + this);
RelativeLayout.LayoutParams menuParams = (RelativeLayout.LayoutParams) mMenuLayout.getLayoutParams();
menuParams.setMargins(mContentWidth - mToggleDistance, 0, 0, 0);
final RelativeLayout.LayoutParams mainParams = (RelativeLayout.LayoutParams) mMainLayout.getLayoutParams();
ValueAnimator animator = ValueAnimator.ofInt(mainParams.leftMargin, mToggled ? -mToggleDistance : 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mainParams.leftMargin = (Integer) valueAnimator.getAnimatedValue();
mMainLayout.requestLayout();
}
});
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {
if (mListener != null) {
mListener.onNavigatorToggled(isToggled);
}
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) {}
});
animator.setDuration(mSpeed);
animator.start();
}
public static interface NavigatorListener {
public void onNavigatorToggled(boolean isToggled);
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
toggleNavigator();
}
return super.onKeyUp(keyCode, event);
}
}
Example usage:
package org.yourdomain.project;
import android.os.Bundle;
import org.yourdomain.app.NavigatorActivity;
public class YourActivity extends NavigatorActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDistance(80); // will leave 20% of the main content in view
setNavigatorListener(new NavigatorListener() {
#Override
public void onNavigatorToggled(boolean isToggled) {
// Load content dynamically, like FB does?
}
});
setContentViews(R.layout.layout_menu, R.layout.activity_your);
}
}
compileSdkVersion 20
minSdkVersion 16
targetSdkVersion 20
Clicking on the physical menu button on your Android device will toggle the navigator.
I plan on using this in a couple of startup projects of my own.
Let me know if you have any questions. I hope this helps you.