I am trying set up a kids app where the can draw on a canvas.
I have a layout which has an ImageView and looks something like this:
The Green background with the letter is the imageview, where the canvas should overlay and allow user to draw on. Also I want to allow the user to change the color stroke by pressing on the color globe.
How can I go on with it?
This answer here might help you with allowing the user the canvas: https://stackoverflow.com/a/7401699/2698582.
To allow the user to draw in different colors, you can use a color picker such as this one: https://code.google.com/p/android-color-picker/, or you can google for a different one.
If you define a new point class and change a bit of this example code, you'll be able to add this color change:
private class ColoredPoint {
public int x, y, color = Color.BLACK;
}
public class DrawView extends View implements OnTouchListener {
List<ColoredPoint> points = new ArrayList<ColoredPoint>();
Paint paint = new Paint();
int currentColor = Color.BLACK;
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(currentColor);
}
#Override
public void onDraw(Canvas canvas) {
for (ColoredPoint point : points) {
paint.setColor(point.color);
canvas.drawCircle(point.x, point.y, 2, paint);
}
}
public boolean onTouch(View view, MotionEvent event) {
ColoredPoint point = new ColoredPoint();
point.color = currentColor;
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
return true;
}
}
Finally, depending on your selected color picker, this code will vary. Basically, you'll add a touch listener to your color globe to show the color popup. Supposing that the popup has an "OK" button, you'll add a button listener to that button. When it is pressed, change the variable "currentColor" to the selected color.
As mentioned in that example post, you can also implement this using Lines. You might consider using a GestureDetector instead. This tutorial should help explain exactly how the GestureDetector works: http://developer.android.com/training/gestures/detector.html.
Take linearlayout and transfer imageview into linearlayout background color. Add your custom view into this linearlayout, and override ondraw method to that view class.
something like this:
<LinearLayout
background:#drawable/foo">
<com.packagename.customview
android:width="match_parent"
android:height="value">
>
</com.packagename.customview>
</LinearLayout>
create customview class and implement drawing part into it.
For the color picker, just use this link: https://code.google.com/p/android-color-picker/
Related
Now I create gui music player with java. In java.swing.JButton, there are square buttons , but I want to customize that button like music player's button. How to make buttons like 'Play Button' in music player? And I also want stop and reset buttons, too.
play button is like ▶ this shape in circle.
stop button is like || this shape in circle.
reset button is like ■ this shape in circle.
thanks for your comment
You could simply set the text of the JButton as the symbol ▶
JButton button = new JButton("▶");
You need to save the .java file with UTF-8 character set though, in eclipse it's really easy as you get a popup.
It's the easiest but least customizable solution.
Another workaround would be to create an image with whatever symbol you wish the button to show. Then add a rectangle to the image's bounds. To check for mouse clicks, simply use a MouseListener and do something similar to this:
if(mouse.isClicked() && rect.contains(mouse.x, mouse.y) { //do stuff }
You can set the play like image to the JButton.
Save your image named "playname" to "path/to/image/" and call as shown in this code:
// JButton play = new JButton();
// assuming play is the place where you've added your JButton
ImageIcon playimg = new ImageIcon("path/to/image/playname");
play.setIcon(playimg);
You can similarly add the same logic for other buttons too.
You could make the button have an image in i, or make the image a button itself.
You can do it by setting the immage you want to the Button. This way you can have a button with Play Icon!
Example:
JButton button= new JButton();
ImageIcon img = new ImageIcon("imgfolder/name.png");
button.setIcon(img);
If you want to make custom button, there is only one easy way to do it. First way is find a custom button library or make your button from image.
Else you can try look on this another post.
Alternatively to using rendered images (like a PNG) and an ImageIcon, you could use Java2D Shapes/Areas.
E.g. to create a Play Area, do something like this:
final GeneralPath play = new GeneralPath();
play.moveTo(1.0/5.0, 1.0/5.0);
play.lineTo(1.0/5.0, 1.0*4.0/5.0);
play.lineTo(1.0*4.0/5.0, 1.0/2.0);
play.closePath();
final Area playArea = new Area(play);
To draw the shape as Icon, use this custom class:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class ShapeIcon implements Icon {
private final Shape shape;
private final Paint paint;
private final Color color;
private final int size;
private final boolean fill;
private final Stroke stroke;
public ShapeIcon(final Shape shape, final Color color, final int size) {
this(shape, color, size, true, new BasicStroke(0.5f));
}
public ShapeIcon(final Shape shape, final Color color, final int size, final boolean fill, final Stroke stroke) {
this.stroke = stroke;
this.fill = fill;
this.color = color;
// allow for customization of fill color/gradient
// a plain color works just as well—this is a little fancier
this.paint = new GradientPaint(0, 12, color.brighter(), 0, 20, color);
this.size = size;
// you could also define different constructors for different Shapes
if (shape instanceof Path2D) {
this.shape = ((Path2D)shape).createTransformedShape(AffineTransform.getScaleInstance(size, size));
} else if (shape instanceof Area) {
this.shape = ((Area) shape).createTransformedArea(AffineTransform.getScaleInstance(size, size));
} else {
this.shape = new Area(shape).createTransformedArea(AffineTransform.getScaleInstance(size, size));
}
}
#Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
final Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(x, y);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (fill) {
g2d.setPaint(paint);
g2d.fill(shape);
}
g2d.setPaint(color);
g2d.setStroke(stroke);
g2d.draw(shape);
g2d.dispose();
}
#Override
public int getIconWidth() {
return size;
}
#Override
public int getIconHeight() {
return size;
}
}
Use the setBorder method of JButton.
roundButton.setBorder(new RoundedBorder(10));
I have created a pulse animation, that takes a location on a map, and continuously emits a pulse (circle). This works as expected. However, I would like the pulse to fade out, the further it is away from the point. Any thoughts on how to achieve this best?
The animation is as follows:
mCircle = mMap.addCircle(
new CircleOptions()
.center(mMarker.getPosition())
.strokeWidth(STROKE_WIDTH)
.strokeColor(Color.parseColor(DEFAULT_COLOR))
.radius(RADIUS));
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setRepeatMode(ValueAnimator.RESTART);
mAnimator.setIntValues(0, 100);
mAnimator.setDuration(DURATION);
mAnimator.setEvaluator(new IntEvaluator());
mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedFraction = valueAnimator.getAnimatedFraction();
mCircle.setRadius(animatedFraction * SIZE);
}
});
mAnimator.start();
I don't think what you want can be accomplished with the provided Google Maps Shapes.
My suggestion would be to do this with a Canvas, Bitmap, and Ground Overlay. You can redraw some shape on the canvas in each callback to onAnimationUpdate() and update the image on the ground overlay. Something like this:
Init Bitmap / Canvas
(This could be the class contructor)
private void init() {
mBitmap = Bitmap.createBitmap(MAX_ANIMATION_WIDTH, MAX_ANIMATION_HEIGHT, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
Update Bitmap
private void updateCanvas() {
// Clear the canvas for new iteration of drawing
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// TODO: determine/update x, y, radius, paint as a function of animation progress
// i.e. for a fade effect, set the paint color to a diminishing alpha level
mPaint.setColor(mPulseColor);
mCanvas.drawCircle(x, y, radius, mPaint);
}
Update ground overlay
private void updateOverlay() {
mGroundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(mBitmap))
}
Add Ground Overlay
(this is the run() method in the original post)
private void addGroundOverlay() {
// Add ground overlay with the bitmap's initial state
mGroundOverlay = mMap.addGroundOverlay(new GroundOverlayOptions());
// TODO: Set values for bitmap's initial state
// Paint the canvas with the initial state
updateCanvas();
// Update the ground overlay
updateOverlay();
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// Update animation values
mPulseColor = Color.RED; // TODO: set color/alpha
// Paint canvas with new animation state
updateCanvas();
// Update overlay
updateOverlay();
}
});
}
I have a HorizontalScrollView in which there are multiple views inside it. I have implemented pinch zoom gesture in which multiple views, which are between my two fingers, are scaled.
But I am facing one glitch. When I am doing pinch zoom, the mid point of pinch zoom is moving but for user experience I want this point to be remain fixed and other things should adjust itself while scaling so that mid point remains static.
Can someone tell me how to do it.
onDraw() method of custom view is
Rect r =new Rect(0, 0, 700, 40);
public void onDraw(Canvas canvas) {
float kk=mScaleFactor; //this variable will be set by pinch zoom event and represent how much to scale
sf*=kk; // this view must scale according to previous scaling
canvas.save();
canvas.scale(sf , 1);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
width=sf*700;
canvas.restore();
requestLayout(); //this will change view width to fit the expanded rectangle
}
onMeasure method is called on requestLayout
#Override protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension((int)width, 340);
}
The above custom view is called 12 times by a subclass of HorizontalScrollView. So there are 12 child views of HorizontalScrollview.
In this class I am doing the following things
Detecting the touch coordinates of two fingers.
Calculating the index of child view on which first finger is touched.
Calculating the index of child view on which second finger is touched.
Passing scale factor of pinch zoom to all the child views between start and last.
These two indices are calculated in previous two step.
And finally invalidate() is called on the child view. So child can scale itself according to scale factor passed by parent view.
But there is one problem here. The mid point of two finger should remain static and other things should adjust during scaling. But my mid point is moving with scaling.
Can someone help me in doing this. Please tell me if any part of code is require.
Code of gesture listener of HorizontalScrollview is
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
ViewGroup pp=takeparent(); //give object of this class
for(int i=start;i<=last;i++)
{
LinearLayout ll=(LinearLayout)pp.getChildAt(i);
DrawView view=(DrawView)ll.findViewById(R.id.drawview);
view.mScaleFactor=mScaleFactor;
view.invalidate();
view.donesf=true;
}
Sample app of mine
Edits as suggested in comments:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
ViewGroup pp=takeparent(); pp contains all the custom child views
//start and last are indices of range of child for which we have to apply gesture
for(int i=start;i<=last;i++)
{
LinearLayout ll=(LinearLayout)pp.getChildAt(i);
DrawView view=(DrawView)ll.findViewById(R.id.drawview);
view.mScaleFactor=mScaleFactor;
view.pivotx=detector.getFocusX()+view.getLeft();
view.pivoty=detector.getFocusY()+view.getTop();
view.invalidate();
}
return true;
}
This is custom view onDraw method
public void onDraw(Canvas canvas) {
sf=mScaleFactor //this scale factor is set by parent view
width=sf*700; //this width will be use to rescale the view's width in requestLayout()
canvas.save();
canvas.scale(sf,1,pivotx,pivoty); //pivotX and pivotY are also set by parent's onScale method of gestureListener
canvas.drawRect(r, paint);
requestLayout();
canvas.restore();
}
For a view you are scaling, you can set the "invariant" point that should not move using setPivotX() and setPivotY(). When I use a ScaleGestureDetector, I use the focus point for this. For example:
#Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
...
mScaledView.setPivotX(detector.getFocusX());
mScaledView.setPivotY(detector.getFocusY());
...
I'm not sure if you want to do this with all of the children, or just the parent view, in your case, but usually you just need to do this to a single "parent" view and it will work properly for the children of that view.
Related to this, I didn't quite understand why you're passing the scaling factor down to each child when scaling the parent view will scale all of its children too. Maybe you just need to add a single FrameLayout (or some other ViewGroup descendent) into your HorizontalScrollView that hosts the children and then just scale that (after setting its pivot appropriately)?
Updated re comment
Given that you only want to scale the views in the pinched region between the fingers, I believe you have a couple options, depending on your app:
1) Dynamically add just those views to an intermediate FrameLayout which gets scaled and has its pivot set, leaving the non-scaled views as direct children of the HorizontalScrollView; or
2) Passing down the focus point to each scaled child view after first adjusting for the child's position. For example, assuming your DrawView either directly or indirectly inherits from android.view.View, which I would strongly recommend, then you can do something like:
for (int i = start; i <= last; i++) {
LinearLayout ll = (LinearLayout)pp.getChildAt(i);
DrawView view = (DrawView)ll.findViewById(R.id.drawview);
view.setScaleX(mScaleFactor);
view.setScaleY(mScaleFactor);
view.setPivotX(detector.getFocusX() - view.getLeft()); // Note: minus, not plus...
view.setPivotY(detector.getFocusY() - view.getTop());
view.invalidate();
}
I'm trying to create a translucent help overlay to be displayed over my activity's main screen when the user first opens the app. I would like to highlight a button contained in the main layout (and inflated using setContentView), by "cutting out" a section of the overlay which corresponds to the position of the button, and make the cutout transparent.
The overlay is a programmatically-created view (which extends RelativeLayout) which is added to my activity's main FrameLayout, like this:
private void addHelpOverlay(){
HelpOverlay help = new HelpOverlay(this);
help.setBackgroundColor(Color.parseColor("#BB222222"));
mainLayer.addView(help);
}
public class HelpOverlay extends RelativeLayout{
public HelpOverlay(Context context){
super(context);
}
#Override
public void dispatchDraw(Canvas canvas){
canvas.drawColor(Color.parseColor("#BB222222"));
Paint mPaint = new Paint();
mPaint.setColor(0xFFFFFF);
mPaint.setAlpha(0);
mPaint.setAntiAlias(true);
canvas.drawCircle(buttonX, buttonY, 100, mPaint);
super.dispatchDraw(canvas);
}
}
The above code doesn't actually show anything, just the translucent layout with no circle cutout. I assume this is because it's just drawing a transparent circle over top of the translucent layout. I'm really struggling to accomplish this, any suggestions would be appreciated!
Try adding PorterDuff to your paint object . which will make the specific area to transparent
Paint mPaint = new Paint();
mPaint.setColor(0xFFFFFF);
mPaint.setAlpha(0);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
canvas.drawCircle(buttonX, buttonY, 100, mPaint);
If you get a picth black in the circle area, that must be due to graphic rendering problem , you can enable it using below code right before you declare paint object .
if (android.os.Build.VERSION.SDK_INT >= 11) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
I guess this should fix your issue
I've been having a lot of issues drawing multiple paths in the same relative layout. What happens is that all my paths are drawn on the same spot they were originally drawn. Instead I want to shrink each drawn path/canvas and have them displayed side by side on the page.
My code to draw the paths looks like
for (int x=0; x < paths.size(); x++){
DrawView dw = new CustomView(this);
dw.path = paths.get(x);
dw.paint = paints.get(x);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (x == 0){
} else {
params.addRule(RelativeLayout.BELOW, x-1);
}
dw.setId(x);
layout.addView(dw, params);
}
I followed other suggestions for TextViews to add an custom layout parameter to display each TextView below each other but this does not appear to work for dynamically drawn paths.
Note: CustomView is a class that extends View and overwrites the onDraw method to draw out my paths.
EDIT:
If it helps my custom class looks like
public class CustomView extends View {
public Path path;
public Paint paint = new Paint();
public CustomView(Context context){
super(context);
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
}
#Override
protected void onDraw(Canvas canvas){
canvas.drawPath(path, paint);
}
}
Is it necessary for you to use a RelativeLayout? If it is not too binding, consider having a LinearLayout which either replaces the RelativeLayout, or is contained within the RelativeLayout. Then you can add all the CustomViews to this LinearLayout, setting the weight field in LinearLayout.LayoutParams to 1.
Alternatively, consider manually setting each CustomView's height to getHeight()/(paths.size()) instead of RelativeLayout.LayoutParams.WRAP_CONTENT.