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
Related
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);
I came across a weird issue of the android framework again:
I have an activity which displays detailed information on an object. It is designed to look like a "floating" activity, meaning it overlays the MainActivity and can be dismissed by a simple swipe down from the user.
Screenshot
How it's done (wrong?)
Because setting the window background to #android:color/transparent lead to ugly side effects, I'm using a custom ImageView as the background (I modified this one by Chris Banes https://github.com/chrisbanes/philm/blob/master/app/src/main/java/app/philm/in/view/BackdropImageView.java):
public class BackdropImageView extends ImageView {
private static final int MIN_SCRIM_ALPHA = 0x00;
private static final int MAX_SCRIM_ALPHA = 0xFF;
private static final int SCRIM_ALPHA_DIFF = MAX_SCRIM_ALPHA - MIN_SCRIM_ALPHA;
private float mScrimDarkness;
private float factor;
private int mScrimColor = Color.BLACK;
private int mScrollOffset;
private int mImageOffset;
private final Paint mScrimPaint;
public BackdropImageView(Context context) {
super(context);
mScrimPaint = new Paint();
factor = 2;
}
public BackdropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mScrimPaint = new Paint();
factor = 2;
}
private void setScrollOffset(int offset) {
if (offset != mScrollOffset) {
mScrollOffset = offset;
mImageOffset = (int) (-offset / factor);
offsetTopAndBottom(offset - getTop());
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setFactor(float factor) {
this.factor = factor;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mScrollOffset != 0) {
offsetTopAndBottom(mScrollOffset - getTop());
}
}
public void setScrimColor(int scrimColor) {
if (mScrimColor != scrimColor) {
mScrimColor = scrimColor;
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setProgress(int offset, float scrim) {
mScrimDarkness = ScrollUtils.getFloat(scrim, 0, 1);
setScrollOffset(offset);
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
// Update the scrim paint
mScrimPaint.setColor(ColorUtils.setAlphaComponent(mScrimColor,
MIN_SCRIM_ALPHA + (int) (SCRIM_ALPHA_DIFF * mScrimDarkness)));
if (mImageOffset != 0) {
canvas.save();
canvas.translate(0f, mImageOffset);
canvas.clipRect(0f, 0f, canvas.getWidth(), canvas.getHeight() + mImageOffset + 1);
super.onDraw(canvas);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mScrimPaint);
canvas.restore();
} else {
super.onDraw(canvas);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mScrimPaint);
}
}
}
When I start the activity, I create a snapshot of the current activity and then save it to cache, passing it's path through Intent.putExtra(String key, String value); :
public static Intent createOverlayActivity(Activity activity) {
Intent startIntent = new Intent(activity, OverlayActivity.class);
View root = activity.findViewById(android.R.id.content);
Rect clipRect = new Rect();
activity.getWindow().getDecorView()
.getWindowVisibleDisplayFrame(clipRect);
Bitmap bitmap = Bitmap.createBitmap(
root.getWidth(),
root.getHeight(),
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawRGB(0xEE, 0xEE, 0xEE);
// Quick fix for status bar appearing in Lollipop and above
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.translate(0, -clipRect.top / 2);
canvas.clipRect(clipRect);
}
root.draw(canvas);
try {
File file = new File(activity.getCacheDir(), "background.jpg");
FileOutputStream stream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream);
stream.flush();
stream.close();
bitmap.recycle();
startIntent.putExtra("bgBitmap", file.getPath());
Log.d(TAG, "Rendered background image.");
} catch (IOException e) {
e.printStackTrace();
}
return startIntent;
}
And in the OverlayActivity's onCreate() I receive the path to the cached file and load the Bitmap into the ImageView:
Bitmap screenshot = BitmapFactory.decodeFile(getIntent().getStringExtra("bgBitmap"));
if (screenshot == null) {
throw new IllegalArgumentException("You have to provide a valid bitmap!");
}
/* BackdropImageView is an ImageView subclass allowing
* me to darken the image with a scrim using the slide offset value */
backdropImageView = new BackdropImageView(this);
backdropImageView.setId(android.R.id.background);
backdropImageView.setFactor(1.125f);
backdropImageView.setScrimColor(Color.BLACK);
backdropImageView.setImageBitmap(screenshot);
backdropImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
The issue
As you can see in the screenshot above, it works pretty decent on devices running API 23, but not on devices below.
Here the image is shown when the activity slides in, until completely covered, but when I the slide down again, the image is gone and the ImageView just shows a solid grey:
Update
I've figured out that the issue has to hide somewhere in the BackdropImageView class, since a simple ImageView works.
Any ideas on what could cause this weird issue?
Thanks in advance!
Ok, seems like I focused too much on the actual image loading than on the view itself.
I found out that the problem was related to the use of offsetTopAndBottom(offset - getTop()); in the subclass. Somehow this makes the ImageView disappear sometimes (still not sure when exactly) on Android Versions previous to Marshmallow, although it was still in the right position in the view hierarchy.
So here's my work-around:
When I removed those lines of code, it worked. Because I needed to offset the view to create a parallax scrolling effect, I moved this functionality out of the view subclass. What I'm doing now is simply calling View.setTranslationY(float) everytime before BackdropImageView.setProgress(int, float) in any scrolling related callback I'm using.
Somehow this works perfectly fine.
I have an ImageView and I am trying to fade from one image to the next using this code:
Drawable bgs[] = new Drawable[2];
public void redraw(int[][] grid) {
bgs[0] = bgs[1];
bgs[1] = new GameDrawable(grid, prefs.colors);
if (bgs[0] == null) {
gameField.setImageDrawable(bgs[1]);
} else {
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
}
gameField is correctly referenced as an ImageView.
gameDrawable simply extends Drawable and draws the grid.
On each move and action the new GameDrawable is being rendered correctly but there is no fading whatsoever. The new image is simply displayed instantaneously. I have tried lengthening the transition time and swapping the order of the drawables with no effect.
Any help on is appreciated.
Update: I have now set my transition to something ridiculously long like 500000. The first drawable shows for a few seconds and then suddenly the second drawable appears. So still no transition.
Update 2:
I think my Drawable might be implemented incorrectly, so I have attached the code.
public class GameDrawable extends Drawable {
private Paint paint = new Paint();
private float blockWidth = 1;
private int[][] myGrid;
private int myColor;
private List<Point> myPoints;
public GameDrawable(int[][] grid) {
super();
this.myGrid = grid;
this.myColor = colors[yourColor];
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0);
this.myPoints = yourPoints;
}
#Override
public void draw(Canvas canvas) {
float height = getBounds().height();
float width = getBounds().width();
blockWidth = width / myGrid.length;
if (height / myGrid.length < blockWidth) {
blockWidth = height / myGrid.length;
}
for (int x = 0; x < myGrid.length; x++) {
for (int y = 0; y < myGrid[x].length; y++) {
paint.setColor(colors[myGrid[x][y]]);
canvas.drawRect(x * blockWidth, y * blockWidth, (x+1)*blockWidth, (y+1)*blockWidth, paint);
}
}
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Looking at your code, I see a problem at the line
bgs[0] = bgs[1];
bgs[1] has not yet been defined before this line and so bgs[0] is null for the first method call. Because of this, (bgs[0] == null) is true, and so the later defined bgs[1] is directly set to the gameField ImageView.
Use corrected code below.
Drawable bgs[] = new Drawable[2];
Drawable firstDrawable = getResources().getDrawable(R.drawable.transparent);
public void redraw(int[][] grid) {
bgs[0] = firstDrawable;
bgs[1] = new GameDrawable(grid, prefs.colors);
firstDrawable = bgs[1];
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
Note that TransitionDrawable does not work properly when the Drawable sizes are different. So you may need to resize firstDrawable beforehand.
EXTRA: I would avoid setCrossFadeEnabled(true) since the whole TransitionDrawable becomes translucent during the transition, revealing the background. Sometimes, this creates a "blinking" effect and destroys the smoothness of the transition.
EDIT: Looking at your custom Drawable implementation, I think the problem lies in the line
canvas.drawColor(Color.WHITE);
in the draw() method.
I looked at TransitionDrawable.java source and found that setAlpha is called on the drawables to get the cross fade effect. However, your canvas has a solid white color and setAlpha() only affects the paint. Hope this is your answer.
EDIT 2: The actual problem, as pointed out by Michael, was that TransitionDrawable's setAlpha() calls on the Drawables were rendered ineffective due to paint.setColor() in the GameDrawable's draw() method overriding the paint's alpha value set by the TransitionDrawable.
I'm trying to do something like this, but I have a little bit of flexibility with how it looks. Essentially either a pie chart with only part of the pie filled (and the rest left blank), or some sort of dial chart.
It would also be relatively easy to use a polar graph to draw two arrows, one at 0 degrees and one at -92 degrees, but I can't find any libraries that will let you do this for Android. I do need it to make 0 degrees actually look like 0 polar degrees.
I've used an AChartEngine DialChart and managed to get something close, but I can't figure out how to get the labels to show up for each arrow. I've tried renderer.setDisplayValues(true); and series.setDisplayChartValues(true);
but it won't show the values for my two arrows, so I'm not sure if it's even possible with a DialChart. I realize that if I showed labels for the dial in the background, my users wouldn't need to have labels on the arrows, but I'm rotating the LinearLayout that the DialChart is added to in order to get 0 to look like 0 degrees in a polar graph. I am also struggling to hide labels for the dial in the background, despite using renderer.setShowLabels(false); and setting just about every other thing you can show to false. My hack is to set the label color to the background color, but if there is a better way to do it, please let me know.
Here is my code for the DialChart.
CategorySeries category = new CategorySeries("Angle");
category.add("Extension", 0);
category.add("Flexion", 90);
renderer = new DialRenderer();
renderer.setLabelsColor(getActivity().getResources().getColor(R.color.background));
renderer.setInScroll(true);
renderer.setDisplayValues(true);
renderer.setShowLegend(false);
renderer.setShowAxes(false);
renderer.setShowLabels(false);
renderer.setShowGrid(false);
renderer.setMargins(new int[] {20, 30, 15, 0});
renderer.setVisualTypes(new DialRenderer.Type[] {Type.ARROW, Type.ARROW});
renderer.setMinValue(-20);
renderer.setMaxValue(280);
renderer.setPanEnabled(false);
renderer.setZoomEnabled(false);
SimpleSeriesRenderer r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
visualizationRenderer.addSeriesRenderer(r);
r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
renderer.addSeriesRenderer(r);
visualization = ChartFactory.getDialChartView(getActivity(), category, renderer);
LinearLayout layout = (LinearLayout) this.getView().findViewById(R.id.sessions_visualization);
layout.addView(visualization);
layout.setRotation(220.0f);
I'm open to either modifying this code to get something that works, or other libraries that will help me accomplish what I'm trying to do. Thanks!
I'm answering my own question for anyone who wants to do something like this later.
You can create custom views in Android and draw whatever you want to display. There is good documentation here.
Here's a relevant code snippet. It's not perfect but it does the job.
public class AngleVisualization extends View {
private Paint textPaint;
private Paint arcPaint;
private Paint linePaint;
RectF oval;
private float extension;
private float flexion;
private int textColor;
private int arcColor;
private float extensionLabelX;
private float extensionLabelY;
private float flexionLabelX;
private float flexionLabelY;
private Rect extensionBounds = new Rect();
public AngleVisualization(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AngleVisualization,
0, 0);
try {
extension = a.getFloat(R.styleable.AngleVisualization_extensionValue, 0);
flexion = a.getFloat(R.styleable.AngleVisualization_flexionValue, 0);
textColor = a.getColor(R.styleable.AngleVisualization_textColor, Color.BLACK);
arcColor = a.getColor(R.styleable.AngleVisualization_arcColor, context.getResources().getColor(R.color.green));
extensionLabelX = a.getDimension(R.styleable.AngleVisualization_extensionLabelX, 190);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_extensionLabelY, 150);
flexionLabelX = a.getDimension(R.styleable.AngleVisualization_flexionLabelX, 50);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_flexionLabelY, 190);
} finally {
a.recycle();
}
oval = new RectF();
init();
}
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(30);
arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setColor(arcColor);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(arcColor);
linePaint.setStrokeWidth(3);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
canvas.drawArc(oval, extension, flexion - extension, true, arcPaint);
canvas.drawLine(0.0f, extensionBounds.height(), oval.right / 2, extensionBounds.height(), linePaint);
canvas.drawText(extensionString, extensionLabelX, extensionLabelY, textPaint);
canvas.drawText(decimalFormat.format(flexion) + "˚", flexionLabelX, flexionLabelY, textPaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
float ww = (float)w - xpad;
float hh = (float)h - ypad;
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
float diameter = Math.min(ww, (hh - extensionBounds.height()) * 2.0f) - extensionBounds.height();
oval = new RectF(
0,
diameter / -2.0f,
diameter,
diameter / 2.0f);
oval.offsetTo(getPaddingLeft(), getPaddingTop() - diameter / 2.0f + extensionBounds.height());
flexionLabelY = diameter / 2.0f + extensionBounds.height();
flexionLabelX = 0;
extensionLabelY = extensionBounds.height();
extensionLabelX = ww / 2;
}
}
I want to show images with alphabets like gmail app as shown in the below figure.
Are all those images are images to be kept in drawable folder or they are drawn as square shapes and then letters are drawn to them? Below is what I tried so far to do dynamically. I got just a square shape. Can someone suggest the way to achieve like in gmail app?
GradientDrawable gd = new GradientDrawable();
gd.mutate();
gd.setColor(getResources().getColor(gColors[i]));
button.setBackgroundDrawable(gd);
Update 2:
I have fixed some of the bugs and released the code as an open source library at: https://github.com/amulyakhare/TextDrawable. It also include some other features that you might want to check out.
Old Answer:
I recommend you to use the following class CharacterDrawable (just copy-paste this):
public class CharacterDrawable extends ColorDrawable {
private final char character;
private final Paint textPaint;
private final Paint borderPaint;
private static final int STROKE_WIDTH = 10;
private static final float SHADE_FACTOR = 0.9f;
public CharacterDrawable(char character, int color) {
super(color);
this.character = character;
this.textPaint = new Paint();
this.borderPaint = new Paint();
// text paint settings
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// border paint settings
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(STROKE_WIDTH);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// draw border
canvas.drawRect(getBounds(), borderPaint);
// draw text
int width = canvas.getWidth();
int height = canvas.getHeight();
textPaint.setTextSize(height / 2);
canvas.drawText(String.valueOf(character), width/2, height/2 - ((textPaint.descent() + textPaint.ascent()) / 2) , textPaint);
}
#Override
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Then using this is simple: new CharacterDrawable('A', 0xFF805781); by passing the character and the color value (example Color.RED or some other color in hex 0xFF805781):
ImageView imageView = (ImageView) findViewById(R.id.imageView);
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781);
imageView.setImageDrawable(drawable);
or based on your question:
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781);
button.setBackgroundDrawable(drawable);
The drawable will scale to fit the size of the ImageView. Result will be:
Update: Updated code for adding a border which is of darker shade (automatically picks a dark shade based on the fill color).
1) Change the value of STROKE_WIDTH based on your needs for the border thikness.
2) Change the value of SHADE_FACTOR for border darkness. If SHADE_FACTOR is small (eg. 0.2f), the border will be darker and vice versa.
Note: You can easily vary the size and font of the character
Simple thing is that you have use Linear Layout and set that background color and set TectView inside that root layout. Its Over.
You should use ColorCode Intesed of images that will good thing compare to use images in terms of loading on UI thread.
<LinearLayout
android:id="#+id/get_more"
android:layout_width="70dp" // this root layout will set your square
android:layout_height="70dp"
android:background="#654321" // set background color of square
android:orientation="horizontal" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="24sp"
android:text="C"
android:background="#ffffff" // Text Color , set as White
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
I tweak the code a little bit..., and it works everytime even with different screen sizes. The trick is to obtain the ImageView canvas size in pixels (which sometimes is density dependent on various devices)
package net.mypapit.android.ui;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
public class CharacterDrawable extends ColorDrawable {
private final char character;
private final Paint textPaint;
private final Paint borderPaint;
private static final int STROKE_WIDTH = 10;
private static final float SHADE_FACTOR = 0.9f;
private int mwidth, mheight;
public CharacterDrawable(char character, int color, int width, int height) {
super(color);
this.character = character;
this.textPaint = new Paint();
this.borderPaint = new Paint();
this.mwidth = width;
this.mheight = height;
// text paint settings
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextAlign(Paint.Align.CENTER);
// border paint settings
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(STROKE_WIDTH);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
public void draw(Canvas canvas) {
super.draw(canvas);
// draw border
canvas.drawRect(getBounds(), borderPaint);
// draw text
int width = this.mwidth;
int height = this.mheight;
textPaint.setTextSize(height / 2);
canvas.drawText(String.valueOf(character), width/2, height/2 - ((textPaint.descent() + textPaint.ascent()) / 2) , textPaint);
}
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Then, refer back to the original Amulya Khare answer:
ImageView imageView = (ImageView) findViewById(R.id.imageView);
CharacterDrawable drawable = new CharacterDrawable('A', 0xFF805781,imageView.getWidth(),imageView.getHeight());
imageView.setImageDrawable(drawable);
It should work on different screen density by now =)