How to implement the Material-design Elevation for Pre-lollipop - android

Google has shown some nice ways that elevation effect are shown on Lollipop here.
android:elevation="2dp"
for buttons,
android:stateListAnimator="#anim/button_state_list_animator"
How can I mimic the elevation effect on pre-Lollipop versions without 3rd party library?

You can mimic the elevation on pre-Lollipop with a official method.
I achieve same effect using,
android:background="#android:drawable/dialog_holo_light_frame"
My tested output:
reference - https://stackoverflow.com/a/25683148/3879847
Thanks to user #Repo..
Update : If you want change color of this drawable try #Irfan answer below ↓
https://stackoverflow.com/a/40815944/3879847

You can't mimic the elevation on pre-Lollipop with a official method.
You can use some drawables to make the shadow in your component. Google uses this way in CardView for example.
The ViewCompat.setElevation(View, int) currently creates the shadow only on API21+. If you check the code behind, this method calls:
API 21+:
#Override
public void setElevation(View view, float elevation) {
ViewCompatLollipop.setElevation(view, elevation);
}
API < 21
#Override
public void setElevation(View view, float elevation) {
}

You can either hack it using a card-view:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/btnGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="#color/accent"
>
<!-- you could also add image view here for icon etc. -->
<TextView
android:id="#+id/txtGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/textSize_small"
android:textColor="#color/primary_light"
android:freezesText="true"
android:text="Get Stuff"
android:maxWidth="120dp"
android:singleLine="true"
android:ellipsize="end"
android:maxLines="1"
/></android.support.v7.widget.CardView>
Or look at using this third party library: https://github.com/rey5137/Material (see wiki article on button https://github.com/rey5137/Material/wiki/Button)

To bring dynamic, animated shadows to pre-Lollipop devices you have to:
Draw a black shape of a view to a bitmap
Blur that shape using elevation as a radius. You can do that using RenderScript. It's not exactly the method Lollipop's using, but gives good results and is easy to add to existing views.
Draw that blurred shape beneath the view. Probably the best place is the drawChild method.
You also have to override setElevation and setTranslationZ, override child view drawing in layouts, turn off clip-to-padding and implement state animators.
That's a lot of work, but it gives the best looking, dynamic shadows with response animations. I'm not sure why you'd like to achieve that without third party libraries. If you wish, you can analyze sources of Carbon and port the parts you'd like to have in your app:
Shadow generation
private static void blurRenderScript(Bitmap bitmap, float radius) {
Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
blurShader.setRadius(radius);
blurShader.setInput(inAllocation);
blurShader.forEach(outAllocation);
outAllocation.copyTo(bitmap);
}
public static Shadow generateShadow(View view, float elevation) {
if (!software && renderScript == null) {
try {
renderScript = RenderScript.create(view.getContext());
blurShader = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
} catch (RSRuntimeException ignore) {
software = true;
}
}
ShadowView shadowView = (ShadowView) view;
CornerView cornerView = (CornerView) view;
boolean isRect = shadowView.getShadowShape() == ShadowShape.RECT ||
shadowView.getShadowShape() == ShadowShape.ROUND_RECT && cornerView.getCornerRadius() < view.getContext().getResources().getDimension(R.dimen.carbon_1dip) * 2.5;
int e = (int) Math.ceil(elevation);
Bitmap bitmap;
if (isRect) {
bitmap = Bitmap.createBitmap(e * 4 + 1, e * 4 + 1, Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
shadowCanvas.drawRect(e, e, e * 3 + 1, e * 3 + 1, paint);
blur(bitmap, elevation);
return new NinePatchShadow(bitmap, elevation);
} else {
bitmap = Bitmap.createBitmap((int) (view.getWidth() / SHADOW_SCALE + e * 2), (int) (view.getHeight() / SHADOW_SCALE + e * 2), Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
if (shadowView.getShadowShape() == ShadowShape.ROUND_RECT) {
roundRect.set(e, e, (int) (view.getWidth() / SHADOW_SCALE - e), (int) (view.getHeight() / SHADOW_SCALE - e));
shadowCanvas.drawRoundRect(roundRect, e, e, paint);
} else {
int r = (int) (view.getWidth() / 2 / SHADOW_SCALE);
shadowCanvas.drawCircle(r + e, r + e, r, paint);
}
blur(bitmap, elevation);
return new Shadow(bitmap, elevation);
}
}
Drawing a view with a shadow
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (!child.isShown())
return super.drawChild(canvas, child, drawingTime);
if (!isInEditMode() && child instanceof ShadowView && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) {
ShadowView shadowView = (ShadowView) child;
Shadow shadow = shadowView.getShadow();
if (shadow != null) {
paint.setAlpha((int) (ShadowGenerator.ALPHA * ViewHelper.getAlpha(child)));
float childElevation = shadowView.getElevation() + shadowView.getTranslationZ();
float[] childLocation = new float[]{(child.getLeft() + child.getRight()) / 2, (child.getTop() + child.getBottom()) / 2};
Matrix matrix = carbon.internal.ViewHelper.getMatrix(child);
matrix.mapPoints(childLocation);
int[] location = new int[2];
getLocationOnScreen(location);
float x = childLocation[0] + location[0];
float y = childLocation[1] + location[1];
x -= getRootView().getWidth() / 2;
y += getRootView().getHeight() / 2; // looks nice
float length = (float) Math.sqrt(x * x + y * y);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
x / length * childElevation / 2,
y / length * childElevation / 2);
canvas.translate(
child.getLeft(),
child.getTop());
canvas.concat(matrix);
canvas.scale(ShadowGenerator.SHADOW_SCALE, ShadowGenerator.SHADOW_SCALE);
shadow.draw(canvas, child, paint);
canvas.restoreToCount(saveCount);
}
}
if (child instanceof RippleView) {
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
child.getLeft(),
child.getTop());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
return super.drawChild(canvas, child, drawingTime);
}
Elevation API backported to pre-Lollipop
private float elevation = 0;
private float translationZ = 0;
private Shadow shadow;
#Override
public float getElevation() {
return elevation;
}
public synchronized void setElevation(float elevation) {
if (elevation == this.elevation)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setElevation(elevation);
this.elevation = elevation;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public float getTranslationZ() {
return translationZ;
}
public synchronized void setTranslationZ(float translationZ) {
if (translationZ == this.translationZ)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setTranslationZ(translationZ);
this.translationZ = translationZ;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public ShadowShape getShadowShape() {
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
}
#Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setTranslationZ(enabled ? 0 : -elevation);
}
#Override
public Shadow getShadow() {
float elevation = getElevation() + getTranslationZ();
if (elevation >= 0.01f && getWidth() > 0 && getHeight() > 0) {
if (shadow == null || shadow.elevation != elevation)
shadow = ShadowGenerator.generateShadow(this, elevation);
return shadow;
}
return null;
}
#Override
public void invalidateShadow() {
shadow = null;
if (getParent() != null && getParent() instanceof View)
((View) getParent()).postInvalidate();
}

Create a 9-patch image with stretchable patches defined on an image with shadow around it.
Add this 9-patch image as a background of your button with a padding so that the shadow is visible.
You can find some pre-defined 9-patch (.9.png) images here or here from where you can select, customize and copy to your project's drawable.

to add #Ranjith Kumar answer
To add background color to the drawable (example button background color), we need to get drawable programatically.
first get the drawable
Drawable drawable = getResources().getDrawable(android.R.drawable.dialog_holo_light_frame);
set the color
drawable.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.color_primary), PorterDuff.Mode.MULTIPLY));
then set it to the view.
view.setBackgroundDrawable(drawable);
in case anyone searching.

u can easily simulate it by declaring a drawable like this -
shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
>
<gradient android:type="linear" android:angle="270" android:startColor="#b6b6b6" android:endColor="#ffffff"/>
</shape>
and use it int ur main xml like -
android:background="#drawable/shadow"

Related

How to design a custom progress bar with rounded edges and slanting progress?

This is what I wish to achieve:
Clicky
The container color, the progress color, the progress background color and the rounded edge radius as well as the thickness should all be editable and modifiable.
How could this be achieved with a light weight custom UI element?
After days of research, I was able to achieve what was expected with clear crisp UI and with all the above requirements and flexibility. The exact above UI can be achieved and follow parameters can be achieved as well:
1. Progress Color
2. Progress background color
3. Container color (Color of container to be set by you, you can set color of rounded edges to match the container color)
4. Height and width of the progress bar to suit your needs.
Here's the code and steps to implement it:
I. Put this code in the attrs.xml file under the values folder
<declare-styleable name="SlantingProgressBar">
<attr name="slantingProgress" format="integer"/>
<attr name="borderRadius" format="integer"/>
<attr name="borderColor" format="integer"/>
<attr name="slantingProgressColor" format="string"/>
<attr name="progressBackgroundColor" format="string"/>
<attr name="slantingProgressFullColor" format="string"/>
</declare-styleable>
II. Create a java class like this:
public class SlantingProgressbar extends View {
private float height = 0;
private float width = 0;
private int borderRadius = 20;
private float progress = 0;
private int rawProgress = 0;
private static final String OPACITY_30_PERCENT = "#66";
private int roundedBorderColor;
private String backgroundColor = "";
private String progressColor = "";
private String progressFullColor = "#fc3d39";
public SlantingProgressbar(Context context) {
super(context);
}
public SlantingProgressbar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SlantingProgressBar,
0, 0);
try {
setProgress(array.getInt(R.styleable.SlantingProgressBar_slantingProgress, 0));
setBackgroundColor(array.getString(R.styleable.SlantingProgressBar_progressBackgroundColor)); //Default color set in the method
setBorderRadius(array.getInt(R.styleable.SlantingProgressBar_borderRadius, 20));
setRoundedBorderColor(array.getInt(R.styleable.SlantingProgressBar_borderColor, 0));
setProgressColor(array.getString(R.styleable.SlantingProgressBar_slantingProgressColor));
} finally {
array.recycle();
}
}
public void setBorderRadius(int borderRadius) {
this.borderRadius = borderRadius;
}
public int getProgress() {
return rawProgress;
}
public void setProgress(int progress) {
if(progress >=0)
{
this.rawProgress = progress;
this.invalidate();
}
else
Log.e("ChlorophyllProgressBar", "Invalid 'progress' value detected, value should be between 0 and 100");
}
public void setRoundedBorderColor(int roundedBorderColor) {
if ( roundedBorderColor == 0) {
this.roundedBorderColor = getResources().getColor(R.color.white);
Log.e("CUSTOM_TAG", "Color set to White: " + this.roundedBorderColor);
return;
}
this.roundedBorderColor = roundedBorderColor;
Log.e("CUSTOM_TAG", "Color set to custom: " + this.roundedBorderColor);
}
private int getRoundedBorderColor()
{
return roundedBorderColor;
}
public void setSlantingProgressFullColor(String color)
{
if (TextUtils.isEmpty(progressFullColor)) {
this.progressFullColor = "#fc3d39";
return;
}
}
public void setBackgroundColor(String backgroundColor) {
if (TextUtils.isEmpty(backgroundColor)) {
this.backgroundColor = "#bfe8d4";
return;
}
this.backgroundColor = backgroundColor;
}
public void setProgressColor(String progressColor) {
if (TextUtils.isEmpty(progressColor)) {
this.progressColor = "#2bb673"; //Green
return;
}
this.progressColor = progressColor;
}
public float getViewHeight() {
return height;
}
public void setViewHeight(float height) {
this.height = height;
}
public float getViewWidth() {
return width;
}
public void setViewWidth(float width) {
this.width = width;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
height = getHeight();
width = getWidth();
progress = getProcessedProgress();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor(backgroundColor));
canvas.drawPaint(paint);
paint.setColor(getProcessedProgressColor());
paint.setAntiAlias(true);
Log.d("CUSTOM_TAG", "Height: " + height);
canvas.drawRect(0, 0, progress, height, paint);
Path triangle = new Path();
triangle.setFillType(Path.FillType.EVEN_ODD);
triangle.moveTo(progress, 0);
triangle.lineTo(progress + height, 0);
triangle.lineTo(progress, height);
triangle.close();
canvas.drawPath(triangle, paint);
drawBorders(canvas, getRoundedBorderColor());
}
private void drawBorders(Canvas canvas, int color) {
float height = getHeight();
float trueWidth = getWidth();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
//paint.setColor(getResources().getColor(R.color.white));
paint.setAntiAlias(true);
Path border = new Path();
border.moveTo(0, 0);
border.lineTo(0, height / 2);
border.quadTo(height / borderRadius, height / borderRadius, height / 2, 0);
border.lineTo(0, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(0, height);
border.lineTo(height / 2, height);
border.quadTo(height / borderRadius, (height - height / borderRadius), 0, height / 2);
border.lineTo(0, height);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, 0);
border.lineTo(trueWidth - (height / 2), 0);
border.quadTo((trueWidth - height / borderRadius), height / borderRadius, trueWidth, height / 2);
border.lineTo(trueWidth, 0);
canvas.drawPath(border, paint);
border.reset();
border.moveTo(trueWidth, height);
border.lineTo(trueWidth - (height / 2), height);
border.quadTo((trueWidth - height / borderRadius), (height - height / borderRadius), trueWidth, height / 2);
border.lineTo(trueWidth, height);
canvas.drawPath(border, paint);
//Adding 1 pixel color
Paint paint1 = new Paint();
paint1.setStyle(Paint.Style.FILL);
int fadedColor = (color & 0x00FFFFFF) | 0x66000000;
Log.d("CUSTOM_TAG", "Faded Color Code: " + fadedColor);
paint1.setColor(fadedColor);
canvas.drawRect(0, 0, 1, height, paint1);
canvas.drawRect(trueWidth-1, 0, trueWidth, height, paint1);
}
private float getProcessedProgress()
{
return (rawProgress == 99) ? ((getWidth() * 98) / 100) : ((getWidth() * rawProgress) / 100);
}
private int getProcessedProgressColor()
{
if(rawProgress > 100)
{
return Color.parseColor(progressFullColor);
}
else
{
return Color.parseColor(progressColor);
}
}
}
III. To use the layout in your xml file:
<com.whatever.package.SlantingProgressbar
android:id="#+id/progressbar_detail"
android:layout_width="match_parent"
android:layout_height="#dimen/dimension1"
android:layout_alignParentLeft="true"
slanting_progress:borderColor="#color/darkgray"
android:layout_below="#id/alphacon_detail"
android:layout_marginBottom="#dimen/budget_list_item_paddingBottom"
android:progress="50" />
I'm sharing this code after a little while, so I might have missed out a thing or two, I'm pretty sure you can get that worked out, please feel free to correct me.
Explanation:
We're using the 'draw' methods in java to implement this feature. The advantage is that, drawing a UI element gives us a sharp and clear UI no matter how big or small you make it.
There might be some hardcoded values, so be sure to edit those before implementing.
Good luck and don't forget to up-vote if this post helps you. Thanks! :)
I'll post an answer here to show some improvements on your code.
You should avoid creating new objects during draw.
draw is called several times again and again to redraw your custom element and all those calls to new Paint() are creating new objects, that needs new memory allocation, and it drives the garbage collector crazy and makes your View much more resource intensive and probably will cause lag on scrolling elements such as RecyclerView.
Alternatively you should have them declared as private Paint border and then private Paint triangle, etc, etc. And then you should initialise the values of All the paints in a separate method and only if the parameters changed. An example code:
private boolean initPaint = false;
private void initPaintsIfNecessary(){
if(!initPaint) return;
initPaint = false;
triangle = new Paint();
triangle.set.... etc
border = new Paint();
border.set.... etc
}
then on all the methods setRoundedBorderColor, setProgressColor, etc. You call initPaint = true; and on the beginning of draw you call initPaintsIfNecessary();. This will avoid all the extra garbage collector work and will allow the UI of your app to run much smoother.
That also includes all the Paint inside drawBorders method.
use format="color" instead ofstring`.
Calling Color.parse(String) is a very slow call and it is very error prone. Alternatively you should the correct color element, like following:
<attr name="slantingProgressColor" format="color"/>
that not just is the correct way, but gives you a color preview on the editor, can be indexed on app style parameters and avoid this inefficient call to parse
then of course you should adjust or method appriately. For example:
setProgressColor(array.getColor(R.styleable.SlantingProgressBar_slantingProgressColor));
getColor will return an integer that can be directly used in paint.setColor(int);
I hope those tips can help you (and others in the community) to create better more efficient View elements. Happy coding!
I know this this old question to answer but this answer may helpful..
You can use drawArc method to achieve this..
RectF oval = new RectF();
oval.set(left, top ,right, bottom);
canvas.drawArc(oval, 270, 360, false, paint);

Translucent gradient status bar on Android Lollipop presented in Material Design

I would like to make something like this
for Android 5.0 and above?
How can I implement this? I can not found any solution on StackOverFlow or on android developer site.
I suggested that I can make status bar transparent and draw gradient drawable under status bar. But there are few problems.
First problem is that usual gradient from shape drawable doesn't support Material Design spec http://www.google.com/design/spec/style/imagery.html
Second problem is that I can not fit map fragment to windows via android:fitsSystemWindows="true".
Formula that gives approximately same plot as shown on the site of Material Design is:
y = 3/(4*(x+0.5)) - 0.5
I've tried several ways to draw hyperboloid gradient via Canvas and found the fastest solution.
public class HyperbolaGradientDrawable extends Drawable {
private static final int ALPHA_DEFAULT = (int) (0.6f * 255);
private int mAlpha = ALPHA_DEFAULT;
private int mColor;
private Rect mBmpRect = new Rect();
private int[] mColors = new int[0];
private Bitmap mBmp;
#Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
if (mColors.length != bounds.height()) {
int alpha;
float y, alphaRelative;
mColors = new int[bounds.height()];
for (int i = 0; i < bounds.height(); i++) {
y = ((float) i) / bounds.height();
// this function gives approximately 0.5 of the bearing alpha at 3/10ths closed to the darker end
alphaRelative = 3 / (4 * (y + 0.5f)) - 0.5f;
alpha = (int) (alphaRelative * mAlpha);
mColors[i] = alpha << 24 | mColor;
}
mBmp = Bitmap.createBitmap(mColors, 1, bounds.height(), Bitmap.Config.ARGB_8888);
mBmpRect.set(0, 0, 1, bounds.height());
}
canvas.drawBitmap(mBmp, mBmpRect, bounds, null);
}
public void setColor(int color) {
// remove alpha chanel
mColor = color & 0x00FFFFFF;
}
#Override
public void setAlpha(int alpha) {
mAlpha = alpha;
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
I know that Google recommend to do not create new objects in draw method, but it works faster than drawing line by line through Canvas.
You can look at comparison of several ways in demo project

Moving "Cloud" - Radial Gradient moving with finger

First Q. I have working code to make this move elsewhere in the file -- that's not the question. The question is how do I create a Radial Gradient that can be moved (below API 16).
Preempting snark, I've spent a lot of time here:
http://developer.android.com/reference/android/graphics/drawable/GradientDrawable.html
With GradientDrawable (below), there doesn't seem to be a way to set the colors without also setting a non-radial orientation.
public class CustomView extends View {
int width = (sWidth/8); // sWidth defined elsewhere as width of screen
int height = (sWidth/8);
GradientDrawable gradient;
int[] colors = {0x60ffffff,0x000000};
public CustomView(Context context) {
super(context);
gradient = new GradientDrawable(GradientDrawable.Orientation.BL_TR,colors);
}
protected void onDraw(Canvas canvas) {
if(x != 0 && y != 0){ // OnTouch calls invalidate on this view for movement
gradient.mutate();
gradient.setShape(GradientDrawable.RADIAL_GRADIENT);
// This just makes it disappear:
// setGradientType (GradientDrawable.RADIAL_GRADIENT);
gradient.setBounds(x-width/2, y-height/2, x + width, y + height);
gradient.draw(canvas);
}
}
}
There is also this:
http://developer.android.com/reference/android/graphics/RadialGradient.html
But there seems to be no way to move that gradient. Can you maybe put the radial gradient on a transparent circle of some kind that can then be moved? I'm at a loss. My thanks in advance.
Edit:
Step 1, define an oval shape in your drawable folder. This one is "cloud.xml":
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<gradient
android:centerX="0.5"
android:centerY="0.5"
android:endColor="#00000000"
android:gradientRadius="30"
android:startColor="#f0ffffff"
android:type="radial" />
<size
android:width="60dp"
android:height="60dp" />
</shape>
The radius, width and height will likely need to be changed dynamically. So put whatever. The color scheme above will give a slightly transparent color to fully transparent. Cloud effect.
Step 2, the constructor of your custom view:
// actually before the constructor this, of course:
GradientDrawable circle;
// now the constructor:
circle = (GradientDrawable) context.getResources().getDrawable(R.drawable.cloud);
Step 3, the onDraw method:
// x & y being coordinates updated from onTouch method,
// circleRad being some constant dependent on screen dp
if(x != 0 && y != 0){
circle.setGradientRadius(circleRad);
circle.setBounds(x-circleRad, y-circleRad,
x+circleRad, y+circleRad);
circle.draw(canvas);
}
------------- Original, Less Process Efficient Solution Preserved Below -----------
Wait a couple weeks and you can answer your own questions. Turns out it was RadialGradient the whole time.
public class CustomView extends View implements OnTouchListener {
Shader radialGradientShader;
Paint paint;
private int circleDiam;
private int x = 0;
private int y = 0;
private int lastScreenColor;
public CustomView(Context context, int circleDiam) {
super(context);
this.circleDiam = circleDiam;
paint = new Paint();
}
protected void onDraw(Canvas canvas) {
if(x != 0 && y != 0){
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
radialGradientShader = new
RadialGradient(x, y, circleDiam,
0xf0ffffff,0x00000000,Shader.TileMode.MIRROR);
paint.setShader(radialGradientShader);
canvas.drawCircle(x, y, circleDiam, paint);
}
}
public boolean onTouch(View v, MotionEvent event) {
x = (int)event.getX();
y = (int)event.getY();
if(event.getAction() == MotionEvent.ACTION_DOWN
&& event.getAction() == MotionEvent.ACTION_MOVE){
invalidate();
return true;
}
else{
x = 0;
y = 0;
invalidate();
return false;
}
}
}
A fluffy cloud!
The only problem with this solution is that Eclipse gets mad when you instantiate an object in the onDraw method. However, if you try to instantiate it in the constructor things get ugly fast.
Extra points for a solution that avoids said problem.

Alpha animation for "fading in" an image?

I have an image that I would like to slowly fade in; I've played around with the standard animation constructs, but it never seems to work.
(Code for the image:)
#Override
public void draw(Canvas c, Layer layer) {
if ( layer == Layer.BACKGROUND ) {
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(4000);
instructions.startAnimation(a);
location.left = (view.getWidth() - instructions.getMinimumWidth() ) /2;
location.right = location.left + instructions.getMinimumWidth();
location.top = view.getHeight() / 2;
location.bottom = location.top + instructions.getMinimumHeight();
instructions.setBounds(location);
instructions.draw(c);
}
//explosions.draw(c, layer);
}
};
... "startAnimation is undefined for the type BitmapDrawable," it says.
The startAnimation is a method of a View, so if you have the bitmap in an ImageView instance imageView, you should call imageView.startAnimation(a);.
Instead of doing it in code, consider doing it using XML: http://developer.android.com/guide/topics/resources/animation-resource.html#View

How can I shrink the drawable on a button?

how can I make the drawable on a button smaller? The icon is too big, actually higher than the button. This is the code I am using:
<Button
android:background="#drawable/red_button"
android:drawableLeft="#drawable/s_vit"
android:id="#+id/ButtonTest"
android:gravity="left|center_vertical"
android:text="S-SERIES CALCULATOR"
android:textColor="#android:color/white"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:drawablePadding="10dp">
</Button>
The upper is how it should look, the lower how it looks right now.
I tried this but there is no image displayed. :-(
Resources res = getResources();
ScaleDrawable sd = new ScaleDrawable(res.getDrawable(R.drawable.s_vit), 0, 10f, 10f);
Button btn = (Button) findViewById(R.id.ButtonTest);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null);
I have found a very simple and effective XML solution that doesn't require ImageButton
Make a drawable file for your image as below and use it for android:drawableLeft
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/half_overlay"
android:drawable="#drawable/myDrawable"
android:width="40dp"
android:height="40dp"
/>
</layer-list>
You can set the image size with android:width and android:height properties.
This way you could at least get the same size for different screens.
The drawback is that it is not exactly like fitXY which would scale image width to fit X and scale image height accordingly.
You should use a ImageButton and specify the image in android:src, and set android:scaletype to fitXY
Setting scaled drawable in code
Drawable drawable = getResources().getDrawable(R.drawable.s_vit);
drawable.setBounds(0, 0, (int)(drawable.getIntrinsicWidth()*0.5),
(int)(drawable.getIntrinsicHeight()*0.5));
ScaleDrawable sd = new ScaleDrawable(drawable, 0, scaleWidth, scaleHeight);
Button btn = findViewbyId(R.id.yourbtnID);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null); //set drawableLeft for example
Buttons do not resize their inner images.
My solution does not require code manipulation.
It uses a layout with TextView and ImageView.
The background of the layout should have the red 3d drawable.
You may need to define the android:scaleType xml attribute.
Example:
<LinearLayout
android:id="#+id/list_item"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:padding="2dp" >
<ImageView
android:layout_width="50dp"
android:layout_height="fill_parent"
android:src="#drawable/camera" />
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="1"
android:gravity="center_vertical"
android:text="Hello - primary" />
</LinearLayout>
BTW:
Counting on different resolution icons may result in a non predictable UI (icon too big or too small)
Text in textview (including in buttons) does not fill the component. This is an Android problem and I don't know how to solve it.
You can use it as an include.
Good luck
Use a ScaleDrawable as Abhinav suggested.
The problem is that the drawable doesn't show then - it's some sort of bug in ScaleDrawables. you'll need to change the "level" programmatically. This should work for every button:
// Fix level of existing drawables
Drawable[] drawables = myButton.getCompoundDrawables();
for (Drawable d : drawables) if (d != null && d instanceof ScaleDrawable) d.setLevel(1);
myButton.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
My DiplayScaleHelper, that works perfectly:
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ScaleDrawable;
import android.widget.Button;
public class DisplayHelper {
public static void scaleButtonDrawables(Button btn, double fitFactor) {
Drawable[] drawables = btn.getCompoundDrawables();
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
if (drawables[i] instanceof ScaleDrawable) {
drawables[i].setLevel(1);
}
drawables[i].setBounds(0, 0, (int) (drawables[i].getIntrinsicWidth() * fitFactor),
(int) (drawables[i].getIntrinsicHeight() * fitFactor));
ScaleDrawable sd = new ScaleDrawable(drawables[i], 0, drawables[i].getIntrinsicWidth(), drawables[i].getIntrinsicHeight());
if(i == 0) {
btn.setCompoundDrawables(sd.getDrawable(), drawables[1], drawables[2], drawables[3]);
} else if(i == 1) {
btn.setCompoundDrawables(drawables[0], sd.getDrawable(), drawables[2], drawables[3]);
} else if(i == 2) {
btn.setCompoundDrawables(drawables[0], drawables[1], sd.getDrawable(), drawables[3]);
} else {
btn.setCompoundDrawables(drawables[0], drawables[1], drawables[2], sd.getDrawable());
}
}
}
}
}
You can call setBounds on the "compound" drawables to modify the size of the image.
Try this code to autosize the drawable of your button:
DroidUtils.scaleButtonDrawables((Button) findViewById(R.id.ButtonTest), 1.0);
defined by this function:
public final class DroidUtils {
/** scale the Drawables of a button to "fit"
* For left and right drawables: height is scaled
* eg. with fitFactor 1 the image has max. the height of the button.
* For top and bottom drawables: width is scaled:
* With fitFactor 0.9 the image has max. 90% of the width of the button
* */
public static void scaleButtonDrawables(Button btn, double fitFactor) {
Drawable[] drawables = btn.getCompoundDrawables();
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
int imgWidth = drawables[i].getIntrinsicWidth();
int imgHeight = drawables[i].getIntrinsicHeight();
if ((imgHeight > 0) && (imgWidth > 0)) { //might be -1
float scale;
if ((i == 0) || (i == 2)) { //left or right -> scale height
scale = (float) (btn.getHeight() * fitFactor) / imgHeight;
} else { //top or bottom -> scale width
scale = (float) (btn.getWidth() * fitFactor) / imgWidth;
}
if (scale < 1.0) {
Rect rect = drawables[i].getBounds();
int newWidth = (int)(imgWidth * scale);
int newHeight = (int)(imgHeight * scale);
rect.left = rect.left + (int)(0.5 * (imgWidth - newWidth));
rect.top = rect.top + (int)(0.5 * (imgHeight - newHeight));
rect.right = rect.left + newWidth;
rect.bottom = rect.top + newHeight;
drawables[i].setBounds(rect);
}
}
}
}
}
}
Be aware, that this may not be called in onCreate() of an activity, because button height and width are not (yet) available there. Call this in on onWindowFocusChanged() or use this solution to call the function.
Edited:
The first incarnation of this function did not work correctly. It used userSeven7s code to scale the image, but returning ScaleDrawable.getDrawable() does not seem to work (neither does returning ScaleDrawable) for me.
The modified code uses setBounds to provide the bounds for the image. Android fits the image into these bounds.
If you want to use 1 image and display it in different size, you can use scale drawable ( http://developer.android.com/guide/topics/resources/drawable-resource.html#Scale ).
I am doing it as below. This creates a 100x100 size image in the button independent of the input image.
drawable.bounds = Rect(0,0,100,100)
button.setCompoundDrawables(drawable, null, null, null)
Not using ScaleDrawable either. Not using button.setCompoundDrawablesRelativeWithIntrinsicBounds() solved my problem, as that seems to use intrinsic bounds (source image size) instead of the bounds you just set.
You can use different sized drawables that are used with different screen densities/sizes, etc. so that your image looks right on all devices.
See here: http://developer.android.com/guide/practices/screens_support.html#support
Did you try wrapping your image in a ScaleDrawable and then using it in your button?
Here the function which I created for scaling vector drawables. I used it for setting TextView compound drawable.
/**
* Used to load vector drawable and set it's size to intrinsic values
*
* #param context Reference to {#link Context}
* #param resId Vector image resource id
* #param tint If not 0 - colour resource to tint the drawable with.
* #param newWidth If not 0 then set the drawable's width to this value and scale
* height accordingly.
* #return On success a reference to a vector drawable
*/
#Nullable
public static Drawable getVectorDrawable(#NonNull Context context,
#DrawableRes int resId,
#ColorRes int tint,
float newWidth)
{
VectorDrawableCompat drawableCompat =
VectorDrawableCompat.create(context.getResources(), resId, context.getTheme());
if (drawableCompat != null)
{
if (tint != 0)
{
drawableCompat.setTint(ResourcesCompat.getColor(context.getResources(), tint, context.getTheme()));
}
drawableCompat.setBounds(0, 0, drawableCompat.getIntrinsicWidth(), drawableCompat.getIntrinsicHeight());
if (newWidth != 0.0)
{
float scale = newWidth / drawableCompat.getIntrinsicWidth();
float height = scale * drawableCompat.getIntrinsicHeight();
ScaleDrawable scaledDrawable = new ScaleDrawable(drawableCompat, Gravity.CENTER, 1.0f, 1.0f);
scaledDrawable.setBounds(0,0, (int) newWidth, (int) height);
scaledDrawable.setLevel(10000);
return scaledDrawable;
}
}
return drawableCompat;
}
Using "BATCH DRAWABLE IMPORT" feature you can import custom size depending upon your requirement example 20dp*20dp
Now after importing use the imported drawable_image as drawable_source for your button
It's simpler this way
It is because you did not setLevel. after you setLevel(1), it will be display as u want
I made a custom button class to achieve this.
CustomButton.java
public class CustomButton extends android.support.v7.widget.AppCompatButton {
private Drawable mDrawable;
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CustomButton,
0, 0);
try {
float mWidth = a.getDimension(R.styleable.CustomButton_drawable_width, 0);
float mHeight = a.getDimension(R.styleable.CustomButton_drawable_width, 0);
Drawable[] drawables = this.getCompoundDrawables();
Drawable[] resizedDrawable = new Drawable[4];
for (int i = 0; i < drawables.length; i++) {
if (drawables[i] != null) {
mDrawable = drawables[i];
}
resizedDrawable[i] = getResizedDrawable(drawables[i], mWidth, mHeight);
}
this.setCompoundDrawables(resizedDrawable[0], resizedDrawable[1], resizedDrawable[2], resizedDrawable[3]);
} finally {
a.recycle();
}
}
public Drawable getmDrawable() {
return mDrawable;
}
private Drawable getResizedDrawable(Drawable drawable, float mWidth, float mHeight) {
if (drawable == null) {
return null;
}
try {
Bitmap bitmap;
bitmap = Bitmap.createBitmap((int)mWidth, (int)mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return drawable;
} catch (OutOfMemoryError e) {
// Handle the error
return null;
}
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomButton">
<attr name="drawable_width" format="dimension" />
<attr name="drawable_height" format="dimension" />
</declare-styleable>
</resources>
Usage in xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.MainActivity">
<com.example.CustomButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="#drawable/ic_hero"
android:text="Avenger"
custom:drawable_height="10dp"
custom:drawable_width="10dp" />
</RelativeLayout>
I tried the techniques of this post but didnot find any of them so attractive. My solution was to use an imageview and textview and align the imageview top and bottom to the textview. This way I got the desired result. Here's some code:
<RelativeLayout
android:id="#+id/relativeLayout1"
android:layout_width="match_parent"
android:layout_height="48dp" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignTop="#+id/textViewTitle"
android:layout_alignBottom="#+id/textViewTitle"
android:src="#drawable/ic_back" />
<TextView
android:id="#+id/textViewBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/textViewTitle"
android:layout_alignBottom="#+id/textViewTitle"
android:layout_toRightOf="#+id/imageView1"
android:text="Back"
android:textColor="#color/app_red"
android:textSize="#dimen/title_size" />
</RelativeLayout>
Using Kotlin Extension
val drawable = ContextCompat.getDrawable(context, R.drawable.my_icon)
// or resources.getDrawable(R.drawable.my_icon, theme)
val sizePx = 25
drawable?.setBounds(0, 0, sizePx, sizePx)
// (left, top, right, bottom)
my_button.setCompoundDrawables(drawable, null, null, null)
I suggest creating an extension function on TextView (Button extends it) for easy reuse.
button.leftDrawable(R.drawable.my_icon, 25)
// Button extends TextView
fun TextView.leftDrawable(#DrawableRes id: Int = 0, #DimenRes sizeRes: Int) {
val drawable = ContextCompat.getDrawable(context, id)
val size = context.resources.getDimensionPixelSize(sizeRes)
drawable?.setBounds(0, 0, size, size)
this.setCompoundDrawables(drawable, null, null, null)
}

Categories

Resources