I want to create a customized ViewGroup which contains some ImageButtons rotated around one circle button.
Every ambient ImageButton was added to my viewgroup has the same layout position, which is on top and horizontal-center of viewgroup. After that, I will rotate each of them with specific angle, target is to create a ring around center button:
public class CustomeView extends ViewGroup {
private List<ArcButton> mArcButtons = new ArrayList<ArcButton>();
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// ...
mArcBtnBound = new RectF(0, 0, (mInnerRadius + mThickness), mThickness);
mArcBtnBound.offsetTo(
getPaddingLeft() + (mInnerRadius + mThickness) / 2,
getPaddingTop());
for(ArcButton arcBtn: mArcButtons) {
arcBtn.layout(
(int)mArcBtnBound.left,
(int)mArcBtnBound.top,
(int)mArcBtnBound.right,
(int)mArcBtnBound.bottom);
}
}
}
mInnerRadius is radius of center circle button, mThickness is height of ambient button in viewgroup. Adding an ambient ImageButton:
ArcButton btn = new ArcButton(getContext());
btn.setBackgroundResource(bkgResId);
btn.setImageResource(R.drawable.ic_launcher);
addView(btn);
btn.rotateTo(10, (mThickness + mInnerRadius) / 2, (mThickness + mInnerRadius));
I was reimplement draw() method of ImageButton class to rotate image view along with background, and provide a public method rotateTo.
public class ArcButton extends ImageButton {
private float mRotation;
private PointF mPivot;
#Override
public void draw(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, mPivot.x, mPivot.y);
super.draw(canvas);
canvas.restore();
}
public void rotateTo(float rotation, float pivotX, float pivotY) {
mPivot.x = pivotX;
mPivot.y = pivotY;
mRotation = rotation;
if (Build.VERSION.SDK_INT >= 11) {
this.setPivotX(pivotX);
this.setPivotY(pivotY);
this.setRotation(rotation);
} else {
draw(new Canvas());
}
}
}
As you can see the above screenshot, problem is my Imagebutton was cutoff. I tried to use RotateAnimation, it made rotated buttons were not cutoff but didnt receive touch event too. Android version on testing is 2.2. Can you help me to fix this?
Any other suggestions to rotate ambient buttons effectively are highly appreciated. Thank you so much!
Related
I'm working on audio recording app.During recording i'm using visualizer like normal audio recording apps.But on specific time a want to draw a drawable on canvas visualizer(that is custom class extend View class).
Like this white drawable dot.
Here is my Custom view class.
public class VisualizerView extends View {
private static final int LINE_WIDTH = 1; // width of visualizer lines
private static final int LINE_SCALE = 75; // scales visualizer lines
private List<VisuallizerItem> visuallizerItems; // amplitudes for line lengths
private int width; // width of this View
private int height; // height of this View
private Paint linePaint; // specifies line drawing characteristics
//Boolean isImage=false;
// constructor
public VisualizerView(Context context, AttributeSet attrs) {
super(context, attrs); // call superclass constructor
linePaint = new Paint(); // create Paint for lines
linePaint.setColor(getResources().getColor(R.color.visualizerColor)); // set color to green
linePaint.setStrokeWidth(LINE_WIDTH); // set stroke width
}
// called when the dimensions of the View change
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w; // new width of this View
height = h; // new height of this View
visuallizerItems = new ArrayList<VisuallizerItem>();
}
// clear all amplitudes to prepare for a new visualization
public void clear() {
visuallizerItems.clear();
}
// add the given amplitude to the amplitudes ArrayList
public void addAmplitude(VisuallizerItem visuallizerItem) {
// isImage=false;
visuallizerItems.add(visuallizerItem); // add newest to the amplitudes ArrayList
// if the power lines completely fill the VisualizerView
if (visuallizerItems.size() * LINE_WIDTH >= width) {
visuallizerItems.remove(0); // remove oldest power value
}
}
public void addAmplitudeImage(VisuallizerItem visuallizerItem) {
//isImage=true;
visuallizerItems.add(visuallizerItem); // add newest to the amplitudes ArrayList
// if the power lines completely fill the VisualizerView
if (visuallizerItems.size() * LINE_WIDTH >= width) {
visuallizerItems.remove(0); // remove oldest power value
}
}
// draw the visualizer with scaled lines representing the amplitudes
#Override
public void onDraw(Canvas canvas) {
int middle = height / 2; // get the middle of the View
float curX = 0; // start curX at zero
// for each item in the amplitudes ArrayList
for (VisuallizerItem power : visuallizerItems) {
if (power.isImage == -100) {
float scaledHeight = power.amplitude / LINE_SCALE; // scale the power
curX += LINE_WIDTH; // increase X by LINE_WIDTH
// draw a line representing this item in the amplitudes ArrayList
canvas.drawLine(curX, middle + scaledHeight / 2, curX, middle
- scaledHeight / 2, linePaint);
} else {//**Here i'm trying to draw mark on canvas**
power.isImage = -100;
//TODO draw attachment image here
Paint p = new Paint();
Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.yellow);
p.setColor(Color.RED);
canvas.drawBitmap(b, visuallizerItems.size(), middle, p);
}
}
}
}
This code add white mark once but remove it when onDraw calls again.
actually i want to draw a mark on canvas like above white dot.
Don't know why it remove it.Please help me
Thanks in advance
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
I have the following class in an Android project:
public class Island extends View {
private ShapeDrawable mDrawable;
public Island(Context context) {
super(context);
int x = 0;
int y = 0;
int width = 50;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
public void change() {
mDrawable.getPaint().setColor(Color.BLACK);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
}
Why am I not able to multiple circles to my layout? Say I have a vertical LinearLayout, then in my on create, I do:
layout.addView(new Island(this));
layout.addView(new Island(this));
Only one circle shows up instead of two different circles. I can't figure out why this is happening. Any idea what is happening to the other circle? Thanks for any ideas.
Edit:
I also tried adding the circles and setting their width and heights to wrap_content, and only one shows up still.
I want to make a small app. You will touch the screen and draw something and it will list points you pass and draw small green 3x3 rectangles for each fifth point. I use onTouchEvent for listing points using TextView and send it to setContentView. However, I have problem in drawing. I checked examples for drawing (onDraw) but I am not able to get it working for both printing point plus drawing green dots. Any help would be great, thanks.
Here you are, a quick sample of drawing on SurfaceView.
public class FunPanel extends SurfaceView {
class Point {
int X;
int Y;
public Point() {
X = Y = -1;
}
}
private ArrayList<Point> mPoints = new ArrayList<Point>();
private Point mCurPoint = new Point();
private Bitmap mBitmap = ....// your desired image
#Override
public void doDraw(Canvas canvas) {
if( !(mPoints.size() % 5) ) {
canvas.drawBitmap(mBitmap, mCurPoint.X, mCurPoint.Y, null);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mCurPoint.X = (int) event.getX() - mBitmap.getWidth() / 2;
mCurPoint.Y = (int) event.getY() - mBitmap.getHeight() / 2;
mPoints.add(mCurPoint);
return super.onTouchEvent(event);
}
}
It's not entirely clear what you're trying to do, but have a look at this It should get you started in the right direction. Basically extend a View and override the onDraw(Canvas) to draw the Rectangles and override the onTouchEvent(MotionEvent) to grab the touch points from the screen.
I have a custom view where I use transformations. So far so good, function like setRotationY(), setScaleX(), setTranslationY() or even getMatrix() work as expected, I’m able manipulate my view and it displays fine.
Where I hit the wall is that a number of function behave strangely after that. For example function like getHitRect() return totally weird values! This is not helping my touch events.
I tried to overload the function but it is still far from working especially when using rotation or scaling (Translation working fine through). I think this as something to do with the fact that the matrix is expressed in child coordinate, so how can I get it in parents coordinate?
#Override
public void getHitRect(Rect outRect){
RectF rect = new RectF();
rect.top = (float) this.getTop();
rect.bottom = (float) this.getBottom();
rect.left = (float) this.getLeft();
rect.right = (float) this.getRight();
this.getMatrix().mapRect(rect);
rect.round(outRect);
}
Can I get some straighter value directly from some function? Like the new Height, Width, top or bottom.
When overriding the “getChildStaticTransformation” method of the ViewGroup or even using transformation function like setRotationY(), setScaleX(), setTranslationY(), getMatrix() (available from API 11) you are only affecting the rendering Matrix. As a result your custom Child View will return Bounds “Rects” far from where your child is getting draw. This is not an issue most of the time but when you start to be willing to click on it.. trouble are starting... Here is how I workaround the issue. I’m sure there might be a better ways but as I haven’t found many things on the subject here it is.
In the ViewGroup Overload:
public interface Itransformable {
public void setTransformationMatrix(Matrix trsMatrix);
}
#Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
if (child instanceof Itransformable){
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
...
// Do whatever transformation you want here
...
((Itransformable)child).setTransformationMatrix(t.getMatrix());
return true;
} else {
return false;
}
}
Here is the Child Custom View:
Note that I'm not storing directly the Transformation Matrix in the custom view but instead the transformed Rect. If you want to go for storing the matrix (i.e. for later transformation like point...) you might need to clone it as the matrix will get altered in some strange way like it is recycle or something.
public class MyCustomView extends View implements MyViewGroup.Itransformable{
private Rect mViewRect = null;
public void setTransformationMatrix(Matrix trsMatrix){
if (trsMatrix!=null){
RectF rect = new RectF();
rect.top = 0;
rect.bottom = (float) this.getHeight();
rect.left = 0;
rect.right = (float) this.getWidth();
trsMatrix.mapRect(rect);
rect.offset((float) this.getLeft(), (float) this.getTop());
if (mViewRect == null) mViewRect = new Rect();
rect.round(mViewRect);
}
}
public Rect getTransformatedRect() {
if (mViewRect!=null){
// OutOfScreen WorkArround - As the view is not Displayed, mViewRect doesn't get updated.
if(getRight() < 0 || getLeft() > mParentWidth){
return new Rect(getLeft(),getTop(),getRight(),getBottom());
} else {
return mViewRect;
}
} else {
return new Rect(getLeft(),getTop(),getRight(),getBottom());
}
}
#Override
public void getHitRect(Rect outRect){
if (mViewRect == null){
super.getHitRect(outRect);
} else {
outRect.set(getTransformatedRect());
}
}