-
-
Save homj/f456dede83cb34a9e997 to your computer and use it in GitHub Desktop.
/* | |
* 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; | |
} | |
} |
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); | |
} | |
} |
This works great, best implementation I've seen so far!
The only issue I see is that it scales too small by default regardless of what size I specify in the constructor parameters. (Looks about 50% small than the size of the play store's icon)
Lowering the scale division fixes it but makes it off-center.
Also, it's missing a way to easily add an x-offset to the right side of the drawable.
@Pkmmte thanks! I'm aware of that, yeah... I'm going to post a updated version soon, where you can apply things such as scaling and bar-thickness
Any chance of a example of how to get this into the actionbar as the drawer icon?
This should do it:
getActionBar().setHomeAsUpIndiactor(drawerIconDrawable);
Do I just copy and paste this into the code of my app?
Yes, just copy it ;) I'm going to post a updated version soon though.. so you might just wait for that
I must be missing something completely obvious to get this working .. :(
mDrawerIcon = new DrawerIconDrawable(50, 50, 16);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setHomeAsUpIndicator(mDrawerIcon);
Is that all that's needed? When doing this, I only see my application icon in the action bar, not the drawable...
UPDATE:
Ok, I got it working! In case anyone has similar issues, the fix for me was to use:
getSupportActionBar().setLogo(mDrawerIcon);
And, then to achieve the spinning effect, in the ActionBarDrawerToggle::onDrawerSlide() set:
mDrawerIcon.setLevel(slideOffset);
And yes, homj, it sounds like you're aware of the scaling issues. It appears ~2x too small, and the incoming size isn't used.
Looking forward to your next update! And, thank you for providing a wonderfully self-contained "library"!!
I just updated the code! You can now set the icon-size besides the drawable-size itself and apply a offset and a gravity to the icon to arrange it.
Feel free to test it and provide some feedback if you notice any bugs or issues!
oops, my fault :P