This Drawable implements the "Drawer-Indicator to Arrow"-Animation as seen in several Material-Design-Apps; NOTE: Mind the updated constructors in Revision 5!
package de.twoid.drawericondrawabletest;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
public class MainActivity extends Activity {
DrawerIconDrawable drawerIconDrawable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
float DIPS = getResources().getDisplayMetrics().density;
drawerIconDrawable = new DrawerIconDrawable(
(int) (72*DIPS) //width of the drawable
, (int) (48*DIPS) //height of the drawable
, (int) (18*DIPS) //x-offset of the icon
, 0 //y-offset of the icon
, Gravity.CENTER_VERTICAL | Gravity.LEFT //let the icon center vertically and align to the left of the drawable
);
getActionBar().setDisplayShowHomeEnabled(false);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeAsUpIndicator(drawerIconDrawable);
}
}
/*
* Copyright 2014 Johannes Homeier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.twoid.drawable;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
public class DrawerIconDrawable extends Drawable {
public static final int STATE_DRAWER = 0;
public static final int STATE_ARROW = 1;
private static final float BASE_DRAWABLE_SIZE = 48f;
private static final float BASE_ICON_SIZE = 18f;
private static final float BASE_BAR_WIDTH = BASE_ICON_SIZE;
private static final float BASE_BAR_HEIGHT = 2f;
private static final float BASE_BAR_SPACING = 5f;
private static final float BASE_BAR_SHRINKAGE = BASE_BAR_WIDTH/6f;
private static final float FULL_ROTATION = 720f;
private static final float TOPRECT_FIRST_ROTATION = 450f;
private static final float TOPRECT_SECOND_ROTATION = FULL_ROTATION-TOPRECT_FIRST_ROTATION;
private static final float MIDRECT_FIRST_ROTATION = 360f;
private static final float MIDRECT_SECOND_ROTATION = FULL_ROTATION-MIDRECT_FIRST_ROTATION;
private static final float BOTRECT_FIRST_ROTATION = 270f;
private static final float BOTRECT_SECOND_ROTATION = FULL_ROTATION-BOTRECT_FIRST_ROTATION;
private static final float LEVEL_BREAKPOINT = 0.5f;
// level of the animation
private float level;
// Dimensions
private int width;
private int height;
private int offsetX;
private int offsetY;
private float barWidth;
private float barHeight;
private float barSpacing;
private float barShrinkage;
// Drawing-Objects
private Paint mPaint;
private Rect iconRect;
private RectF topRect;
private RectF middleRect;
private RectF bottomRect;
private int gravity = Gravity.CENTER;
private boolean breakpointReached = false;
/**
* Create a new DrawerIconDrawableV1 with size in pixel
*
* @param size the size (both width and height) this drawable should have in pixel
*/
public DrawerIconDrawable(int size) {
this(size, size);
}
/**
* Create a new DrawerIconDrawableV1 with a specfied width and height in pixel
* @param width the width this drawable should have in pixel
* @param height the height this drawable should have in pixel
*/
public DrawerIconDrawable(int width, int height) {
this(width, height, 0, 0);
}
/**
* Create a new DrawerIconDrawableV1 with specified width and height in pixel and also apply a {@link Gravity} to align the icon
* @param width the width this drawable should have in pixel
* @param height the height this drawable should have in pixel
* @param gravity the gravity to align the icon in this drawable
*/
public DrawerIconDrawable(int width, int height, int gravity) {
this(width, height, 0, 0, gravity);
}
/**
* Create a new DrawerIconDrawableV1 with specified width and height in pixel and also apply a offset to the icon
* @param width the width this drawable should have in pixel
* @param height the height this drawable should have in pixel
* @param offsetX the offset the icon should have from its center in x-direction
* @param offsetY the offset the icon should have from its center in y-direction
*/
public DrawerIconDrawable(int width, int height, int offsetX, int offsetY) {
this(width, height, 0, 0, Gravity.CENTER);
}
/**
* Create a new DrawerIconDrawableV1 with specified width and height in pixel and also apply a offset to the icon plus a {@link Gravity} to align the icon
* @param width the width this drawable should have in pixel
* @param height the height this drawable should have in pixel
* @param offsetX the offset the icon should have from its center in x-direction
* @param offsetY the offset the icon should have from its center in y-direction
* @param gravity the gravity to align the icon in this drawable
*/
public DrawerIconDrawable(int width, int height, int offsetX, int offsetY, int gravity) {
this.width = width;
this.height = height;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.gravity = gravity;
setBounds(new Rect(0, 0, width, height));
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(0xffffffff);
iconRect = new Rect();
topRect = new RectF();
middleRect = new RectF();
bottomRect = new RectF();
setDefaultIconSize();
setLevel(0);
}
@Override
public void draw(Canvas canvas) {
canvas.translate(iconRect.left + offsetX, iconRect.top + offsetY);
float scaleFactor = level < LEVEL_BREAKPOINT ? level * 2 : (level - 0.5f) * 2;
drawTopRect(canvas, scaleFactor);
drawMiddleRect(canvas, scaleFactor);
drawBottomRect(canvas, scaleFactor);
}
private void drawTopRect(Canvas canvas, float scaleFactor) {
canvas.save();
offsetTopRect(barShrinkage * scaleFactor, 0, -barShrinkage * scaleFactor, 0);
canvas.rotate(
level < LEVEL_BREAKPOINT
? level * TOPRECT_FIRST_ROTATION
: LEVEL_BREAKPOINT*TOPRECT_FIRST_ROTATION + (1 - level) * TOPRECT_SECOND_ROTATION
, iconRect.width()/2
, iconRect.height()/2);
canvas.drawRect(topRect, mPaint);
canvas.restore();
}
private void drawMiddleRect(Canvas canvas, float scaleFactor) {
canvas.save();
offsetMiddleRect(0, 0, -barShrinkage*2f/3f * scaleFactor, 0);
canvas.rotate(
level < LEVEL_BREAKPOINT
? level * MIDRECT_FIRST_ROTATION
: LEVEL_BREAKPOINT*MIDRECT_FIRST_ROTATION + (1 - level) * MIDRECT_SECOND_ROTATION
, iconRect.width()/2
, iconRect.height()/2);
canvas.drawRect(middleRect, mPaint);
canvas.restore();
}
private void drawBottomRect(Canvas canvas, float scaleFactor) {
canvas.save();
offsetBottomRect(barShrinkage * scaleFactor, 0, -barShrinkage * scaleFactor,
0);
canvas.rotate(
level < LEVEL_BREAKPOINT
? level * BOTRECT_FIRST_ROTATION
: LEVEL_BREAKPOINT*BOTRECT_FIRST_ROTATION + (1 - level) * BOTRECT_SECOND_ROTATION
, iconRect.width()/2
, iconRect.height()/2);
canvas.drawRect(bottomRect, mPaint);
canvas.restore();
}
private void offsetTopRect(float offsetLeft, float offsetTop,
float offsetRight, float offsetBottom) {
topRect.set(
iconRect.width()/2 - barWidth/2 + offsetLeft
, iconRect.height()/2 - barSpacing - barHeight/2 + offsetTop
, iconRect.width()/2 + barWidth/2 + offsetRight
, iconRect.height()/2 - barSpacing + barHeight/2 + offsetBottom);
}
private void offsetMiddleRect(float offsetLeft, float offsetTop,
float offsetRight, float offsetBottom) {
middleRect.set(
iconRect.width()/2 - barWidth/2 + offsetLeft
, iconRect.height()/2 - barHeight/2 + offsetTop
, iconRect.width()/2 + barWidth/2 + offsetRight
, iconRect.height()/2 + barHeight/2 + offsetBottom);
}
private void offsetBottomRect(float offsetLeft, float offsetTop,
float offsetRight, float offsetBottom) {
bottomRect.set(
iconRect.width()/2 - barWidth/2 + offsetLeft
, iconRect.height()/2 + barSpacing - barHeight/2 + offsetTop
, iconRect.width()/2 + barWidth/2 + offsetRight
, iconRect.height()/2 + barSpacing + barHeight/2 + offsetBottom);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
invalidateSelf();
}
@Override
public int getOpacity() {
return 0;
}
/**
* set the color of the Drawable;
* @param color
*/
public void setColor(int color) {
mPaint.setColor(color);
invalidateSelf();
}
/**
* set the size of the icon; this size should be smaller than the size of this drawable itself to fully show the transformation!
* @param size the size of the icon in pixel
*/
public void setIconSize(int size){
if(size > Math.min(width, height)) size = Math.min(width, height);
iconRect.set(0,0,size,size);
Gravity.apply(gravity, iconRect.width(), iconRect.height(), getBounds(), iconRect);
float ratio = size / BASE_ICON_SIZE;
barWidth = BASE_BAR_WIDTH * ratio;
barHeight = BASE_BAR_HEIGHT * ratio;
barSpacing = BASE_BAR_SPACING * ratio;
barShrinkage = BASE_BAR_SHRINKAGE * ratio;
invalidateSelf();
}
/**
* Apply a {@link Gravity} to align the icon in this drawable
* @param gravity the gravity to align the icon in this drawable
*/
public void setGravity(int gravity){
Gravity.apply(gravity, iconRect.width(), iconRect.height(), getBounds(), iconRect);
invalidateSelf();
}
/**
* resets the icon size to its default size (as specified in the Material-Design-guidelines
* this means, the icon will be 1/3 of the minimal size of this drawable
*/
public void setDefaultIconSize(){
setIconSize((int) (Math.min(width, height) * BASE_ICON_SIZE/BASE_DRAWABLE_SIZE));
}
/**
* offset the icon from its center
* @param offsetX the offset the icon should have from its center in x-direction
* @param offsetY the offset the icon should have from its center in y-direction
*/
public void offsetIcon(int offsetX, int offsetY){
this.offsetX = offsetX;
this.offsetY = offsetY;
invalidateSelf();
}
/**
* set the state of the Drawable;
*
* @param level
*/
public void setState(int state) {
switch (state) {
case STATE_DRAWER:
setLevel((float) STATE_DRAWER);
break;
case STATE_ARROW:
setLevel((float) STATE_ARROW);
break;
}
}
/**
* set the level of the animation; drawer indicator is fully displayed at 0;
* arrow is fully displayed at 1
*
* @param level
*/
public void setLevel(float level) {
if (level == 1)
breakpointReached = true;
if (level == 0)
breakpointReached = false;
this.level = (breakpointReached ? LEVEL_BREAKPOINT : 0) + level / 2;
invalidateSelf();
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
public int getIntrinsicHeight() {
return height;
}
}