I want to make a custom button like this program does with perhaps a radial gradient.
I subclassed view, and draw three shape drawables and then draw the text. the text seems off center, so I tried to draw a bounding rectangle for the text, but no luck there. and was planning on adding a click listener to get button like behaviour.
perhaps I should subclass button, but where to draw my drawables so they don't get messed up by the button's text being drawn.
Any pointers will be appreciated.
thanks
Edit2: see second attempt below.
Edit3: the reason for the bounty is to figure out why subclassing drawable does not work. the gradient is not so important.
edit4: discovered drawRect before getTextBounds() in DrawableView::OnDraw().
package acme.drawables;
import android.content.*;
import android.content.pm.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.graphics.drawable.shapes.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.*;
import android.view.Menu;
import android.view.View;
import android.widget.*;
import static java.lang.Math.*;
public class MainActivity extends AppCompatActivity {
DrawableView drawableView;
LinearLayout row(boolean isRow1) {
LinearLayout layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(w,d);
int m=(int)round(w*margin);
layoutParams.setMargins(m,m,m,m);
for(int i=0;i<n;i++)
layout.addView(drawableView=new DrawableView(this,i,isRow1),layoutParams);
return layout;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
DisplayMetrics metrics=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
w=d=(int)round(metrics.densityDpi);
LinearLayout row1=row(true);
LinearLayout row2=row(false);
LinearLayout layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(row1);
layout.addView(row2);
LinearLayout l=new LinearLayout(this);
setContentView(layout);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
public class DrawableView extends View {
public DrawableView(Context context,int column,boolean isRow1) {
super(context);
setBackgroundColor(Color.TRANSPARENT);
this.column=column;
text=""+(char)('0'+column);
int r=(int)round(w*radius);
d0=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
d0.getPaint().setColor(0xff000000);
d0.setBounds(0,0,w,d);
d1=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
d1.getPaint().setColor(on[column]);
d1.setBounds(edge,edge,w-edge,d-edge);
d2=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
int b=(int)round(w*border);
d2.setBounds(b/2,b/2,w-b/2,d-b/2);
d2.getPaint().setColor(isRow1?on[column]:off[column]);
}
protected void onDraw(Canvas canvas) {
d0.draw(canvas);
d1.draw(canvas);
d2.draw(canvas);
Paint paint = new Paint();
//paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
//canvas.drawPaint(paint);
paint.setColor(Color.CYAN);
paint.setTextSize(w*95/100);
Rect r=new Rect();
paint.getTextBounds(text,0,1,r); // were switched
canvas.drawRect(r,paint); // were switched
int x=(w-r.width())/2,y=(d-r.height())/2;
paint.setColor(Color.BLACK);
canvas.drawText(text,x,d-y,paint);
}
final int column;
final String text;
ShapeDrawable d0, d1, d2;
}
final int n=5, edge=1;
double margin=.10, radius=.05, border=.15;
int w, d;
final int[] on=new int[]{0xffff0000,0xffffff00,0xff00ff00,0xff0000ff,0xffff8000};
final int[] off=new int[]{0xff800000,0xff808000,0xff008000,0xff000080,0xff804000};
}
This version tries to subclass drawable and use a button. but the button's drawing seems to interfere with drawing my drawable shapes. it looks like the bounds are being ignored.
package acme.drawables;
import android.content.*;
import android.content.pm.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.graphics.drawable.shapes.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.*;
import android.view.Menu;
import android.view.View;
import android.widget.*;
import static java.lang.Math.*;
public class MainActivity extends AppCompatActivity {
LinearLayout row(boolean isRow1) {
LinearLayout layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(w,d);
int m=(int)round(w*margin);
layoutParams.setMargins(m,m,m,m);
if(true)
for(int i=0;i<n;i++) { // subclass drawable
Button b=new Button(this);
b.setText(""+(char)('0'+i));
b.setBackground(new MyDrawable(i,i/n%2==0));
layout.addView(b,layoutParams);
}
else
for(int i=0;i<n;i++) // use drawable view with canvas draw text
layout.addView(drawableView=new DrawableView(i,isRow1),layoutParams);
return layout;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
DisplayMetrics metrics=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
w=d=(int)round(metrics.densityDpi);
LinearLayout row1=row(true);
LinearLayout row2=row(false);
LinearLayout layout=new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(row1);
layout.addView(row2);
LinearLayout l=new LinearLayout(this);
setContentView(layout);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
public class MyDrawable extends Drawable {
public MyDrawable(int column,boolean isRow1) {
drawableView=new DrawableView(column,isRow1);
}
public void setAlpha(int alpha) {
System.out.println("ignoring set alpha to: "+alpha);
}
public void setColorFilter(ColorFilter colorFilter) {
System.out.println("ignoring set color filter to: "+colorFilter);
}
public int getOpacity() {
return PixelFormat.OPAQUE;
}
public void draw(Canvas canvas) {
System.out.println(this+" is drawing.");
drawableView.d0.draw(canvas);
System.out.println("d0 bounds: "+drawableView.d0.getBounds());
drawableView.d1.draw(canvas);
System.out.println("d1 bounds: "+drawableView.d1.getBounds());
drawableView.d2.draw(canvas);
System.out.println("d2 bounds: "+drawableView.d2.getBounds());
}
final DrawableView drawableView; // cheat by delegating
}
public class DrawableView extends View {
public DrawableView(int column,boolean isRow1) {
super(MainActivity.this);
setBackgroundColor(Color.TRANSPARENT);
this.column=column;
text=""+(char)('0'+column);
int r=(int)round(w*radius);
d0=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
d0.getPaint().setColor(0xff000000);
d0.setBounds(0,0,w,d);
d1=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
d1.getPaint().setColor(on[column]);
d1.setBounds(edge,edge,w-edge,d-edge);
d2=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
int b=(int)round(w*border);
d2.setBounds(b/2,b/2,w-b/2,d-b/2);
d2.getPaint().setColor(isRow1?on[column]:off[column]);
}
protected void onDraw(Canvas canvas) {
d0.draw(canvas);
d1.draw(canvas);
d2.draw(canvas);
Paint paint=new Paint();
//paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
//canvas.drawPaint(paint);
paint.setColor(Color.CYAN);
paint.setTextSize(w*95/100);
Rect r=new Rect();
canvas.drawRect(r,paint);
paint.getTextBounds(text,0,1,r);
int x=(w-r.width())/2, y=(d-r.height())/2;
paint.setColor(Color.BLACK);
canvas.drawText(text,x,d-y,paint);
}
final int column;
final String text;
ShapeDrawable d0, d1, d2;
}
DrawableView drawableView;
final int n=5, edge=1;
double margin=.10, radius=.05, border=.15;
int w, d;
final int[] on=new int[]{0xffff0000,0xffffff00,0xff00ff00,0xff0000ff,0xffff8000};
final int[] off=new int[]{0xff800000,0xff808000,0xff008000,0xff000080,0xff804000};
}
1) Use alignment to draw text at the center in your DrawableView (should help with the text seems off center):
paint.setTextAlign(Align.CENTER); // <- should help you with centering
paint.getTextBounds(text, 0, 1, r);
int x = w / 2, y = (d - r.height()) / 2; // <- was updated too
2) To answer your question the reason for the bounty is to figure out why subclassing drawable does not work:
I suppose it's because you create DrawableView in MyDrawable and don't add it to any container which means you don't measure and layout it. So, it's probably zero height and width.
3) I would suggest you to use Button instead of custom views and drawables. You extend from Button and do additional drawings in the end of onDraw method, something like this:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// your custom drawing over button
}
Original Incorrect Answer
the reason for the bounty is to figure out why subclassing drawable does not work
Try to check if you need to call:
super.onDraw(canvas) in DrawableView.onDraw
super.draw(canvas) in MyDrawable.draw
use this code to make gradient button
Button your_button= findViewById(R.id.button);
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[] {0xFF616261,0xFF131313});
gd.setCornerRadius(0f);
your_button.setBackgroundDrawable(gd);
It is not a good idea to create Drawable depended on a View.
As Eugen Pechanec suggested, make MyDrawable and DrawableView static.
You are using ShapeDrawables only in MyDrawable, so you can move it from DrawableView.
It can be something like this:
public static class MyDrawable extends Drawable {
private ShapeDrawable d0, d1, d2;
private int edge;
private int border;
public MyDrawable(int color1, int color2, int radius, int edge, int border) {
this.edge = edge;
this.border = border;
float[] outerRadii = new float[] {
radius, radius,
radius, radius,
radius, radius,
radius, radius
};
d0 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
d0.getPaint().setColor(0xff000000);
d1 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
d1.getPaint().setColor(color1);
d2 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
d2.getPaint().setColor(color2);
}
public void setAlpha(int alpha) {
System.out.println("ignoring set alpha to: " + alpha);
}
public void setColorFilter(ColorFilter colorFilter) {
System.out.println("ignoring set color filter to: " + colorFilter);
}
public int getOpacity() {
return PixelFormat.OPAQUE;
}
public void draw(Canvas canvas) {
System.out.println(this + " is drawing.");
d0.draw(canvas);
System.out.println("d0 bounds: " + d0.getBounds());
d1.draw(canvas);
System.out.println("d1 bounds: " + d1.getBounds());
d2.draw(canvas);
System.out.println("d2 bounds: " + d2.getBounds());
}
#Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
d0.setBounds(left, top, right, bottom);
d1.setBounds(left + edge, top + edge, right - edge, bottom - edge);
d2.setBounds(left + border / 2, top + border / 2,
right - border / 2, bottom - border / 2);
}
}
You can consider to not use ShapeDrawable and draw the shapes by yourself:
public static class MyDrawable extends Drawable {
private int radius;
private int edge;
private int border;
private RectF bounds1 = new RectF();
private RectF bounds2 = new RectF();
private RectF bounds3 = new RectF();
private Paint paint1 = new Paint();
private Paint paint2 = new Paint();
private Paint paint3 = new Paint();
public MyDrawable(int color1, int color2, int radius, int edge, int border) {
this.radius = radius;
this.edge = edge;
this.border = border;
float[] outerRadii = new float[] {
radius, radius,
radius, radius,
radius, radius,
radius, radius
};
paint1.setColor(0xff000000);
paint2.setColor(color1);
paint3.setColor(color2);
}
public void setAlpha(int alpha) {
System.out.println("ignoring set alpha to: " + alpha);
}
public void setColorFilter(ColorFilter colorFilter) {
System.out.println("ignoring set color filter to: " + colorFilter);
}
public int getOpacity() {
return PixelFormat.OPAQUE;
}
public void draw(Canvas canvas) {
canvas.drawRoundRect(bounds1, radius, radius, paint1);
canvas.drawRoundRect(bounds2, radius, radius, paint2);
canvas.drawRoundRect(bounds3, radius, radius, paint3);
}
#Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
bounds1.set(left, top, right, bottom);
bounds2.set(bounds1);
bounds2.inset(edge, edge);
bounds3.set(bounds1);
bounds3.inset(border / 2, border / 2);
}
}
By the way, it is good to use StateListDrawable for a Button.
So you can use MyDrawable like this:
MyDrawable drawable = new MyDrawable(...);
MyDrawable drawablePressed = new MyDrawable(...);
MyDrawable drawableFocused = new MyDrawable(...);
StateListDrawable stateDrawable = new StateListDrawable();
stateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed);
stateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused);
stateDrawable.addState(new int[]{}, drawable);
Button button = (Button) findViewById(R.id.button);
button.setBackground(stateDrawable);
Related
I am new to coding and to java and android. I am making an app that has multiple canvas views (where a person can draw lines with their finger) in multiple fragments (requested to be like this). I have managed to get the fragments to work and to draw lines on each canvas. Next, I want to be able to save the lines drawn by the user when the user goes to a different fragment or closes the app and when the user comes back to a fragment, the drawing for that fragment will still be there, and can continue editing it by adding more lines or clear it by hitting Clear, the same should happen for other fragments that should have their respective drawings.
I was previously told about the interface Parcelable and have made some steps to implement it but I am finding it hard to understand what I am doing and having trouble reading in an ArrayList. The error is stating that it is expecting ClassLoader but being provided with an ArrayList (I hope in the right place), and that's if I make the getter static but that creates an error: Non-static field 'paths' cannot be referenced from a static context in the getter in the PaintView class (this controls the drawing). I get a similar static context message for the readArrayList method in the FingerPath class. Once this is figured out, I will need to find out how to bring the relevant, saved drawing back to the canvas when the fragment is opened which I am not sure how, I think when the Bundle isn't equal to null but not sure.
I'll show the code below (right now it is on a test app)
FingerPath class
import android.graphics.Path;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
public class FingerPath implements Parcelable {
public int color;
public boolean emboss;
public boolean blur;
public int strokeWidth;
public Path path;
public FingerPath(int color, boolean emboss, boolean blur, int strokeWidth, Path path) {
this.color = color;
this.emboss = emboss;
this.blur = blur;
this.strokeWidth = strokeWidth;
this.path = path;
}
protected FingerPath(Parcel in) {
color = in.readInt();
emboss = in.readByte() != 0;
blur = in.readByte() != 0;
strokeWidth = in.readInt();
//line below causing me issues
path = in.readArrayList(PaintView.getPaths());
}
public static final Creator<FingerPath> CREATOR = new Creator<FingerPath>() {
#Override
public FingerPath createFromParcel(Parcel in) {
return new FingerPath(in);
}
#Override
public FingerPath[] newArray(int size) {
return new FingerPath[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(color);
parcel.writeByte((byte) (emboss ? 1 : 0));
parcel.writeByte((byte) (blur ? 1 : 0));
parcel.writeInt(strokeWidth);
//parcel.writeParcelableArray(PaintView.getPaths(), 0);
}
}
PaintView
Controls what happens when the user starts drawing
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.EmbossMaskFilter;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
public class PaintView extends View {
public static int BRUSH_SIZE = 10;
public static final int DEFAULT_COLOR = Color.WHITE;
public static int DEFAULT_BG_COLOR = Color.GRAY;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private Path mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
public PaintView(Context context) {
this(context, null);
}
public PaintView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
}
//the getter in question
public ArrayList getPaths() {
return paths;
}
public void init(DisplayMetrics metrics) {
int height = metrics.heightPixels;
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR;
strokeWidth = BRUSH_SIZE;
}
public void normal() {
emboss = false;
blur = false;
}
public void clear() {
backgroundColor = DEFAULT_BG_COLOR;
paths.clear();
normal();
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
mCanvas.drawColor(backgroundColor);
for (FingerPath fp: paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
mCanvas.drawPath(fp.path, mPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
private void touchStart(float x, float y) {
mPath = new Path();
FingerPath fp = new FingerPath(currentColor, emboss, blur, strokeWidth, mPath);
paths.add(fp);
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x-mX);
float dy = Math.abs(y-mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
invalidate();
break;
}
return true;
}
}
Here's an example of a fragment
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class LineLHwFragment extends Fragment {
private PaintView paintView;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_line_l_hw, container, false);
paintView = v.findViewById(R.id.lineLPaintView);
DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
paintView.init(metrics);
setHasOptionsMenu(true);
return v;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.normal:
paintView.normal();
return true;
case R.id.clear:
paintView.clear();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("line test", paintView.getPaths());
}
}
hello I am working on a android launcher and I want it to where there is a circle shape behind every app icon I am referencing the shape via the java code NOT XML! So I used this code as my shape:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<stroke android:color="#ccc" android:width="2dp" />
<solid android:color="#ffffff"/>
<size android:width="100dp" android:height="100dp"/>
</shape>
</item>
</selector>
and here's my class that makes the app irons for the launcher:
package appname.widget;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.widget.ImageView;
import appname.R;
import appname.util.AppManager;
import appname.util.DragAction;
import appname.util.GoodDragShadowBuilder;
import appname.util.LauncherSettings;
import appname.util.Tool;
import static android.R.attr.button;
import static android.R.attr.width;
import static com.bennyv5.materialpreffragment.R.attr.height;
/**
* Created by BennyKok on 10/23/2016.
*/
public class AppItemView extends View implements Drawable.Callback{
public Drawable getIcon() {
return icon;
}
public void setIcon(Drawable icon, boolean isStatic) {
this.icon = icon;
this.icon.setCallback(this);
}
#Override
public void refreshDrawableState() {
invalidateDrawable(icon);
super.refreshDrawableState();
}
#Override
public void invalidateDrawable(Drawable drawable) {
invalidate();
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public float getIconSize() {
return iconSize;
}
public void setIconSize(float iconSize) {
this.iconSize = iconSize;
}
private float iconSize;
private Drawable icon;
private String label;
public boolean isShortcut;
public Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Rect mTextBound = new Rect();
private boolean noLabel,vibrateWhenLongPress;
private float labelHeight;
public AppItemView(Context context) {
super(context);
init();
}
public AppItemView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
setWillNotDraw(false);
labelHeight = Tool.convertDpToPixel(14,getContext());
textPaint.setTextSize(sp2px(getContext(),14));
textPaint.setColor(Color.DKGRAY);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (label != null && !noLabel){
textPaint.getTextBounds(label,0,label.length(),mTextBound);
}
//The height should be the same as they have the same text size.
float mHeight = iconSize + (noLabel? 0 : labelHeight);
float heightPadding = (getHeight() - mHeight)/2f;
if (label != null && !noLabel) {
float x = (getWidth()-mTextBound.width())/2f;
if (x < 0)
x = 0;
canvas.drawText(label,x, getHeight() - heightPadding, textPaint);
}
if (icon != null){
canvas.save();
canvas.translate((getWidth()-iconSize)/2,heightPadding);
icon.setBounds(0,0,(int)iconSize,(int)iconSize);
icon.draw(canvas);
canvas.restore();
}
}
public static class Builder{
AppItemView view;
public Builder(Context context){
view = new AppItemView(context);
float iconSize = Tool.convertDpToPixel(LauncherSettings.getInstance(view.getContext()).generalSettings.iconSize, view.getContext());
view.setIconSize(iconSize);
}
public AppItemView getView(){
return view;
}
public Builder setAppItem(AppManager.App app){
view.setIcon(app.icon,true);
view.setLabel(app.appName);
view.setBackgroundResource(R.drawable.iconbackbg);
view.setScaleX(0.75f); // <- resized by scaling
view.setScaleY(0.75f);
return this;
}
public Builder withOnClickLaunchApp(final AppManager.App app){
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Tool.createScaleInScaleOutAnim(view, new Runnable() {
#Override
public void run() {
Tool.startApp(view.getContext(), app);
}
});
}
});
return this;
}
public Builder withOnLongClickDrag(final AppManager.App app,final DragAction.Action action,#Nullable final OnLongClickListener eventAction){
withOnLongClickDrag(Desktop.Item.newAppItem(app),action,eventAction);
view.setScaleX(0.75f); // <- resized by scaling
view.setScaleY(0.75f);
return this;
}
public Builder withOnLongClickDrag(final Desktop.Item item, final DragAction.Action action, #Nullable final OnLongClickListener eventAction){
view.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
if (view.vibrateWhenLongPress)
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Intent i = new Intent();
i.putExtra("mDragData", item);
ClipData data = ClipData.newIntent("mDragIntent", i);
v.startDrag(data, new GoodDragShadowBuilder(v), new DragAction(action), 0);
if (eventAction != null)
eventAction.onLongClick(v);
return true;
}
});
return this;
}
public Builder withOnTouchGetPosition(){
view.setOnTouchListener(Tool.getItemOnTouchListener());
return this;
}
public Builder setTextColor(#ColorInt int color){
view.textPaint.setColor(color);
return this;
}
public Builder setNoLabel(){
view.noLabel = true;
return this;
}
public Builder vibrateWhenLongPress(){
view.vibrateWhenLongPress = true;
return this;
}
public Builder setShortcutItem(final Intent intent){
view.isShortcut = true;
view.setScaleX(0.75f); // <- resized by scaling
view.setScaleY(0.75f);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Tool.createScaleInScaleOutAnim(view, new Runnable() {
#Override
public void run() {
view.getContext().startActivity(intent);
}
});
}
});
view.setIcon(Tool.getIconFromID(view.getContext(),intent.getStringExtra("shortCutIconID")),true);
view.setLabel(intent.getStringExtra("shortCutName"));
return this;
}
}
}
EDIT I Tried suggestion but the circle does not show up at all heres the Updated On draw method:
#Override
protected void onDraw(Canvas canvas) {
//Load your oval shape
Drawable background = getResources().getDrawable(R.drawable.iconbackbg, getContext().getTheme());
//create a LayerDrawable that combines the oval shape with the icon
Drawable[] layers = {background, icon};
LayerDrawable layerDrawable = new LayerDrawable(layers);
//set the gravity of both drawables to center
layerDrawable.setLayerGravity(0, Gravity.CENTER);
layerDrawable.setLayerGravity(1, Gravity.CENTER);
super.onDraw(canvas);
if (label != null && !noLabel){
textPaint.getTextBounds(label,0,label.length(),mTextBound);
}
//The height should be the same as they have the same text size.
float mHeight = iconSize + (noLabel? 0 : labelHeight);
float heightPadding = (getHeight() - mHeight)/2f;
if (label != null && !noLabel) {
float x = (getWidth()-mTextBound.width())/2f;
if (x < 0)
x = 0;
canvas.drawText(label,x, getHeight() - heightPadding, textPaint);
}
if (layerDrawable != null){
canvas.save();
canvas.translate((getWidth()-iconSize)/2,heightPadding);
layerDrawable.setLayerWidth(1, (int) iconSize);
layerDrawable.setLayerHeight(1, (int) iconSize);
layerDrawable.draw(canvas);
canvas.restore();
}
if (icon != null){
canvas.save();
canvas.translate((getWidth()-iconSize)/2,heightPadding);
icon.setBounds(0,0,(int)iconSize,(int)iconSize);
icon.draw(canvas);
canvas.restore();
}
}
the circle shape is showing up its just resized very funky heres a screenshot of what it looks link on a nexus 7 tablet:
any help would be amazing!
Thanks in advance :)
I guess R.drawable.iconbackbg is the reference for the ShapeDrawable?
If so, setting it as the background of your View makes it draw with the View's bounds. So if your View's width and height are not equal, it will draw an oval as your background instead of a circle (hence the name of the shape "oval").
If you want to draw it as a circle, you need to override the drawables bounds in some way. That's not possible when using platform drawables and setting them as the background of a view, as the view will override the drawables bounds during draw().
A possible solution would be to wrap the app's icon together with your oval ShapeDrawable in a LayerDrawable (A LayerDrawable is basically a list of drawables that get drawn on top of each other):
private void init(){
//... do your initilizations
//Load your oval shape
background = getResources().getDrawable(R.drawable.iconbackbg, getContext().getTheme());
}
public void setIcon(Drawable icon){
//... set the icon, be sure to remove callbacks to the previously set icon!
//create a LayerDrawable that combines the oval shape with the icon
LayerDrawable layerDrawable = new LayerDrawable(background, icon);
//set the gravity of both drawables to center
layerDrawable.setLayerGravity(0, Gravity.CENTER);
layerDrawable.setLayerGravity(1, Gravity.CENTER);
}
and then in your onDraw() do:
if (layerDrawable != null){
canvas.save();
canvas.translate((getWidth()-iconSize)/2,heightPadding);
layerDrawable.setLayerWidth(1, (int) iconSize);
layerDrawable.setLayerHeight(1, (int) iconSize);
layerDrawable.draw(canvas);
canvas.restore();
}
I'm new to Android development, and trying to render a button to a Canvas object in an onDraw method, basically text over a backfield. It's a good way to get my feet wet with some of the rendering commands.
I am able to fill a solid rectangle and then draw centered text over it, but when I try to fill a gradient rectangle, and then draw text over it, the text doesn't draw.
Code is below, cobbled together from various examples. Basically:
DO_PAINT=0, DO_GRADIENT=0 -> text renders
DO_PAINT=1, DO_GRADIENT=0 -> solid rectangle with text on top
DO_PAINT=0, DO_GRADIENT=1 -> gradient rectangle (no text) !!!
So something about my gradient drawing interferes with my text rendering. I'm guessing that I'm leaving something in a bad state in the Paint object, but I'm not sure what property that would be...
Any insight or thoughts are greatly appreciated...
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.view.View;
import android.graphics.Paint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Canvas;
import android.util.Log;
import android.view.MotionEvent;
import android.app.Activity;
public class cMyView extends View
{
public cMyView(Context context, Activity owner_activity)
{
super(context);
}
final Paint m_paint = new Paint();
public String m_Text = "Button";
private final Rect textBounds = new Rect();
public Typeface m_TypeFace = Typeface.create("Arial",Typeface.NORMAL);
public int m_TextColor = Color.argb(255,0,0,0);
public int m_TextSize = 32;
#Override
protected void onDraw(Canvas canvas) {
Rect m_Bounds = new Rect(100,100,500,200);
boolean DO_PAINT = false;
boolean DO_GRADIENT = true;
if ( DO_PAINT) {
m_paint.setStyle(Paint.Style.FILL);
m_paint.setColor(Color.GREEN);
canvas.drawRect(m_Bounds, m_paint);
}
if (DO_GRADIENT) {
m_paint.setShader(new LinearGradient(0, m_Bounds.top, 0, m_Bounds.bottom, Color.BLACK, Color.WHITE, Shader.TileMode.MIRROR));
canvas.drawRect(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom, m_paint);
}
m_paint.setColor(m_TextColor);
m_paint.setTextSize(m_TextSize);
m_paint.setTypeface(m_TypeFace);
m_paint.getTextBounds(m_Text, 0, m_Text.length(), textBounds);
double x = m_Bounds.left + m_Bounds.width()/2 - textBounds.exactCenterX();
double y = m_Bounds.top + m_Bounds.height()/2 - textBounds.exactCenterY();
canvas.drawText(m_Text, (float) x, (float) y, m_paint);
}
}
Just add an another Paint for text, this is worked for me, and I found the reason, if you comment 2-nd row in DO_GRADIENT case (in your code), then will see that text is gradient, it mean that it draw, but have same gradient as background have, and becomes invisible.
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
final Paint m_paint = new Paint();
public String m_Text = "Button";
private final Rect textBounds = new Rect();
public Typeface m_TypeFace = Typeface.create("Arial",Typeface.NORMAL);
public int m_TextColor = Color.argb(255, 0, 0, 0);
public int m_TextSize = 32;
private final Paint textPaint = new Paint();
#Override
protected void onDraw(Canvas canvas) {
Rect m_Bounds = new Rect(100,100,500,200);
boolean DO_PAINT = true;
boolean DO_GRADIENT = true;
if ( DO_PAINT) {
m_paint.setStyle(Paint.Style.FILL);
m_paint.setColor(Color.GREEN);
canvas.drawRect(m_Bounds, m_paint);
}
if (DO_GRADIENT) {
m_paint.setShader(new LinearGradient(0, m_Bounds.top, 0, m_Bounds.bottom, Color.BLACK, Color.WHITE, Shader.TileMode.MIRROR));
canvas.drawRect(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom, m_paint);
}
m_paint.setColor(m_TextColor);
m_paint.setTextSize(m_TextSize);
m_paint.setTypeface(m_TypeFace);
m_paint.getTextBounds(m_Text, 0, m_Text.length(), textBounds);
double x = m_Bounds.left + m_Bounds.width()/2 - textBounds.exactCenterX();
double y = m_Bounds.top + m_Bounds.height()/2 - textBounds.exactCenterY();
textPaint.setColor(m_TextColor);
textPaint.setTextSize(m_TextSize);
textPaint.setTypeface(m_TypeFace);
canvas.drawText(m_Text, (float) x, (float) y, textPaint);
}
I'm trying to create some pixels with a random color by passing the color into the constructor as I'm creating the object. I can't seem to do it no matter what I try.
Here is my code..
Object to be drawn
public class Particle extends View{
private int locationX;
private int locationY;
private int sizeX;
private int sizeY;
private Paint color;
private Rect rect;
public Particle(Context context, int locationX, int locationY, int sizeX, int sizeY, Paint color) {
super(context);
// TODO Auto-generated constructor stub
this.locationX = locationX;
this.locationY = locationY;
this.sizeX = sizeX;
this.sizeY = sizeY;
this.color = color;
rect = new Rect();
color = new Paint();
}
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
rect.set(0,0, sizeX, sizeY);
color.setStyle(Paint.Style.FILL);
//this is where it fails, the color defaults to black.
//will not take color = new Paint(Color.RED); from MainActivity
//as parameter.
color.set(color);
canvas.drawRect(rect, color);
}
}
My MainActivity
public class MainActivity extends Activity {
private Particle particle;
private Paint color;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
color = new Paint(Color.RED);
particle = new Particle(this, 0, 0, 450, 120, color);
setContentView(particle);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
You are already overriding your colorwith new Paint() in the constructor. Then when you call color.set(Color) you are not changing it.
I suggest you pass the color as int in the constructor and then use setColor
public Particle(Context context, int locationX, int locationY, int sizeX, int sizeY, int paintColor) {
(...)
color = new Paint();
color.setColor(paintColor);
}
I have a Rect object which I draw using drawRect(Rect, Paint). I have a OnTouchEvent that checks if the touch position is inside the Rect. If the touch is inside the Rect then something on my screen changes.
The issue I'm having is: Where I have to touch to get the on touch statements to execute is around 50 pixels higher than the Rect object.
Here is the relevant parts of my code:
public class MainActivity extends Activity {
DrawView drawView;
RelativeLayout fLY;
static Rect newGame = new Rect(LEFT, 165 + 35, 320, 200 + 35);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
fLY = new RelativeLayout(this);
drawView = new DrawView(this);
fLY.addView(drawView);
setContentView(fLY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int touchX = (int)event.getX();
int touchY = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
if(newGame.contains(touchX, touchY))reset();
}
}
And my DrawView class:
public class DrawView extends View{
Paint paint = new Paint();
int COLORS[] = Constants.COLORS;
int left = 250;
int size = 35;
public DrawView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawRect(MainActivity.newGame, paint);
}
}
It's probably off by exactly the height of the Status Bar. You can get the height of the status bar through this answer: Height of statusbar?
Then add that to your getY and it should work.