What I am trying to achieve is to make an arc shaped seekbar. I know there are plenty of libraries I could use to achieve this, but I am just trying my hands on custom made views.
I have encountered couple of problems:
I have a class which extends SeekBar, and I have implemented onDraw and onMeasure methods as well, but I am not able to view that in layout editor in eclipse, here is the code for the custom view class:
package com.custom.android.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.SeekBar;
import android.widget.Toast;
public class CustomSeekBar extends SeekBar {
public CustomSeekBar(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public CustomSeekBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void draw(Canvas canvas) {
// TODO Auto-generated method stub
super.draw(canvas);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Here is my layout xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<com.custom.android.views.CustomSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/seekBar"/>
</RelativeLayout>
If I use canvas class to draw an arc or any shape, would that be a good starting point?
What exactly is wrong with the eclipse adt and how could I use the onDraw method to give shape to that seekbar?
Drawing a ProgressBar with any shape, is pretty easy. With the SeekBar you have some complexity, since you have to achieve 3 diferent things:
Draw the line
Draw the draggable thumb, if you want.
Handle the user interaction
You have to think of it as an arc that is draw inside a rectangle. So point 3 could be easy: just let the user move the finger in a horizontal line, or exactly over the arc, but considering only the x coordinate of the touch event. What does this mean, in short? ok, good news: you dont have to do anything, since thats the normal behavior of the base SeekBar.
For the second point, you can choose an image for the handler, and write it in the corresponding position with a little maths. Or you can forget the handler for know, and just draw the seek bar as a line representing the full track, and another line over it representing the progress. When you have this working, if you want you can add the handler.
And for the first point, this is the main one, but its not hard to achieve. You can use this code:
UPDATE: I made some improvements in the code
public class ArcSeekBar extends SeekBar {
public ArcSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ArcSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Paint mBasePaint;
private Paint mProgressPaint;
private RectF mOval = new RectF(5, 5, 550, 550);
private int defaultmax = 180;
private int startAngle=180;
private int strokeWidth=10;
private int trackColor=0xFF000000;
private int progressColor=0xFFFF0000;
public void setOval(RectF mOval) {
this.mOval = mOval;
}
public void setStartAngle(int startAngle) {
this.startAngle = startAngle;
}
public void setStrokeWidth(int strokeWidth) {
this.strokeWidth = strokeWidth;
}
public void setTrackColor(int trackColor) {
this.trackColor = trackColor;
}
public void setProgressColor(int progressColor) {
this.progressColor = progressColor;
}
public ArcSeekBar(Context context) {
super(context);
mBasePaint = new Paint();
mBasePaint.setAntiAlias(true);
mBasePaint.setColor(trackColor);
mBasePaint.setStrokeWidth(strokeWidth);
mBasePaint.setStyle(Paint.Style.STROKE);
mProgressPaint = new Paint();
mProgressPaint.setAntiAlias(true);
mProgressPaint.setColor(progressColor);
mProgressPaint.setStrokeWidth(strokeWidth);
mProgressPaint.setStyle(Paint.Style.STROKE);
setMax(defaultmax);// degrees
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawArc(mOval, startAngle, getMax(), false, mBasePaint);
canvas.drawArc(mOval, startAngle, getProgress(), false, mProgressPaint);
invalidate();
//Log.i("ARC", getProgress()+"/"+getMax());
}
}
Of course, you can and you should make everything configurable, be means of the contructor, or with some setters for the start and end angles, dimensions of the containing rectangle, stroke widths, colors, etc.
Also, note that the arc is drawn from 0 to getProgress, being this number an angle relative to the x axis, growing clocwise, so, if it go from 0 to 90 degrees, it will be something like:
Of course you can change this: canvas.drawArc get any number as an angle, and it is NOT treated as module 360, but you can do the maths and have it starting and ending in any point you want.
In my example the beggining is in the 9 of a clock, and it takes 180 degrees, to the 3 in the clock.
UPDATE
I uploaded a running example to github
Related
I'd like to animate the drawing of a path, i.e. to have it progressively appear on the screen. I am using the canvas and my best guess so far is to use an ObjectAnimator to take care of the animation. However, I cannot figure out how to actually draw the corresponding segment of the path in the onDraw() method. Is there a method that would allow to do this? Would I need to involve path effects for that?
Edit: Using a DashPathEffect and setting its "on" and "off" intervals in the animation to cover the part of the path we want to draw for that step seems to work here, but it requires allocating a new DashPathEffect for every step of the animation. I will leave the question open in case there is a better way.
Answering my own question, as I figured out a satisfying way to do that.
The trick is to use an ObjectAnimator to progressively change the current length of the stroke, and a DashPathEffect to control the length of the current stroke. The DashPathEffect will have its dashes parameter initially set to the following:
float[] dashes = { 0.0f, Float.MAX_VALUE };
First float is the length of the visible stroke, second length of non-visible part. Second length is chosen to be extremely high. Initial settings thus correspond to a totally invisible stroke.
Then everytime the object animator updates the stroke length value, a new DashPathEffect is created with the new visible part and set to the Painter object, and the view is invalidated:
dashes[0] = newValue;
mPaint.setPathEffect(new DashPathEffect(dashes, 0));
invalidate();
Finally, the onDraw() method uses this painter to draw the path, which will only comprise the portion we want:
canvas.drawPath(path, mPaint);
The only drawback I see is that we must create a new DashPathEffect at every animation step (as they cannot be reused), but globally this is satisfying - the animation is nice and smooth.
package com.nexoslav.dashlineanimatedcanvasdemo;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
public class CustomView extends View {
float[] dashes = {30, 20};
Paint mPaint;
private Path mPath;
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(10f);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setPathEffect(new DashPathEffect(dashes, 0));
mPath = new Path();
mPath.moveTo(200, 200);
mPath.lineTo(300, 100);
mPath.lineTo(400, 400);
mPath.lineTo(1000, 200);
mPath.lineTo(1000, 1000);
mPath.lineTo(200, 400);
ValueAnimator animation = ValueAnimator.ofInt(0, 100);
animation.setInterpolator(new LinearInterpolator());
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Log.d("bla", "bla: " + valueAnimator.getAnimatedValue());
mPaint.setPathEffect(new DashPathEffect(dashes, (Integer) valueAnimator.getAnimatedValue()));
invalidate();
}
});
animation.setDuration(1000);
animation.setRepeatMode(ValueAnimator.RESTART);
animation.setRepeatCount(ValueAnimator.INFINITE);
animation.start();
}
public CustomView(Context context) {
super(context);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mPaint);
}
}
As far as I know, the only way is to start with an empty path and have a runnable that appends points to the path at defined intervals, until it is completed.
Does anyone knows if its possible to make the RatingBar go from right-to-Left instead of left to right, or have an idea how to do it?
You can use android:rotation="180" in your Ratingbar to rotate it
i used this way on my read-only Ratingbars, i dont know if this will effect interacting with the Ratingbar.
Use attribute android:scaleX="-1"
OR
package com.example.selection;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RatingBar;
public class InvertRatingBar extends RatingBar {
public InvertRatingBar(Context context) {
super(context);
}
public InvertRatingBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public InvertRatingBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int width = this.getMeasuredWidth();
event.setLocation(width - event.getX(), event.getY());
return super.onTouchEvent(event);
}
#Override
protected synchronized void onDraw(Canvas canvas) {
int width = this.getMeasuredWidth();
Matrix matrix = canvas.getMatrix();
matrix.preTranslate(width, 0);
matrix.preScale(-1.f, 1);
canvas.setMatrix(matrix);
super.onDraw(canvas);
}
}
You have to customize the RatingBar..
However you just have to play tricks with displaying RatingBar Images & then some sort of reverse calculations.
Trick will be displaying reverse Images in RatingBar.. swap selected and unselected images in RatingBar.
Allow user to rate on RatingBar. You have to swap selected and unselected calculations. I mean you have to do reverse calculations.
Complete Working example of custom RatingBar on StackOverflow answered by me only.
Download RatingBar icons from here : http://developer.android.com/guide/practices/ui_guidelines/icon_design.html
I need your experience.
Problem : I need to be able to draw thing (rect, circ,etc) on one part of a FlipperView....
My main.xml has a main linearLayout. In this LinearLayout I have a ViewFlipper with 2 linearlayouts in it. The first linearlayout has soms buttons, inputfiels,etc... the second one should have a special view in wich I can draw the things I choose in the first part.
So I have created a new view wich extends the View class so I can play with the ondraw methode. But I can not get it to work.
This is what I have so far...
MAIN.XML
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="#+id/layout_main" xmlns:android="http://schemas.android.com/apk/res/android">
<ViewFlipper android:id="#+id/details" android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:orientation="vertical" android:layout_height="match_parent">
//BUTTONS TEXTFIELDS ETC
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:orientation="vertical" android:layout_height="match_parent">
//an instance of my new ViewClass
<Vierbergen.Tim.ViewClass
android:id="#+id/draw" android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</ViewFlipper>
</LinearLayout>
The VIEWCLASS.java
public class ViewClass extends View {
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
paint.setColor(Color.WHITE);
}
#Override
public void onDraw(Canvas canvas) {
//depending on some params....
draw this, draw that...
}
}
and then my main activity
DRAWER.JAVA
public class SmsDraw extends Activity implements OnTouchListener{
ViewClass vClass;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
vClass = (ViewClass) findViewById(R.id.draw);
}
// with somewhere a draw function excecuted by a button
private void start() {
//where can I get a canvas ? Canvas c = new Canvas();
blablalba
vClass.onDraw(c);
}
So I need to be able to draw on the thing VIEWCLASS with id = draw in my main.xml...
How can I do this ? please help me with an explanation and solution and not just a solution :-)
Thanks VeeTee
Your onDraw method will be called by the framework if your View is attached to the view hierarchy. You don't need to call it yourself.
If you're unsure about your onDraw code, try using the code from a sample like DrawPoints in API Demos.
You don't call onDraw yourself, as Matthew has said. Here's a very simple addition to your ViewClass that would allow you to draw rectangles or circles. It hasn't been tested, so proceed carefully.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class DrawView extends View {
private boolean drawRect;
private boolean drawCircle;
private float rect_w;
private float rect_h;
private int rect_color;
private float circle_radius;
private int circle_color;
Paint paint;
public DrawView(Context context) {
super(context);
paint = new Paint();
}
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
}
public DrawView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint();
}
public void drawRect(float w, float h, int color) {
// define these variables privately at the top of the class
this.drawRect = true;
this.drawCircle = false;
this.rect_w = w;
this.rect_h = h;
this.rect_color = color;
}
public void drawCircle(float radius, int color) {
// define these variables privately at the top of the class
this.drawRect = false;
this.drawCircle = true;
this.circle_radius = radius;
this.circle_color = color;
}
#Override
public void onDraw(Canvas canvas) {
if (drawRect) {
paint.setColor(this.rect_color);
canvas.drawRect(0, 0, rect_w, rect_h, paint);
}
if (drawCircle) {
paint.setColor(this.circle_color);
canvas.drawCircle(0, 0, circle_radius, paint);
}
}
}
Then in your view Class, call it like this:
vClass = (DrawView) findViewById(R.id.draw);
vClass.drawRect(3,4,0x334434);
Seems like the problem it wasn't working the first time was...
when the main activity want to setContentView(...)... it crashes....
But when I leave it out of the xml
and create it at runtime like this
viewClass = new ViewClass(this);
layke = (LinearLayout) findViewById(R.id.layoutDraw);//wich is the second part of the flipperview
layke.addView(viewClass); // to at it to the right layout
it works....
I am trying to make a program the makes a graph using user input values from 2 EditText Fields. The program should use this input to draw a line from the center of an axis to a point specified by the information. I have a button that each time it is clicked should make a new line to the point specified (so there can be more than one line) I have created a custom view to hold the axis, but that utilizes its onDraw method, obviously, so I cant use it also to draw the new line.
Here is the code for my custom view:
package android.physicsengine;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class AxisDrawing extends View{
public AxisDrawing(Context context){
super(context);
}
public AxisDrawing(Context context, AttributeSet attrs){
super(context, attrs);
}
public AxisDrawing(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
#Override
protected void onDraw(Canvas canvas){
canvas.drawColor(Color.BLACK);
Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.RED);
canvas.drawLine(canvas.getWidth()/2, canvas.getHeight()/2-200, canvas.getWidth()/2 ,canvas.getHeight()/2+100, linePaint);
canvas.drawLine(canvas.getWidth()/2-150, canvas.getHeight()/2-75, canvas.getWidth()/2+150 ,canvas.getHeight()/2-75, linePaint);
}
}
If your custom view class is defined in the activity, then it is an inner class of that activity and has access to variables and arrays which are defined on the activity level.
Every time the user clicks the button, you should process and store the information into these common variables or arrays which onDraw can access and from them calculate the next line or the entire graph. If your custom view is a separate class, then you need to pass the data, one way to do it is to use static varibales..
to make onDraw() method draw again your graph you need to state:
myCustomView.invalidate();
in the button click event, just after you set the new data for the graph.
You simply need to set the data in the custum view (Globally ) and call invalidate which will redraw the view.
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.util.AttributeSet;
importandroid.view.View;
#Override
protectedvoidonDraw(Canvascanvas
{
canvas.drawColor(Color.BLACK);
PaintlinePaint=newPaint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.RED);
canvas.drawLine(data,data,getWidtt()-data,getHeight()-data,linePaint);
//you can also pplaceinvalidate() here which will recursively redraw the canvas in aloop
}
publicvoidsetData(intdata)
{
this.data=data;
invalidate();
}
}
How can you display upside down text with a textview in Android?
In my case I have a 2 player game, where they play across from each other. I want to display test to the second player oriented to them.
This was the solution I implemented after AaronMs advice
The class that does the overriding, bab.foo.UpsideDownText
package bab.foo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.TextView;
public class UpsideDownText extends TextView {
//The below two constructors appear to be required
public UpsideDownText(Context context) {
super(context);
}
public UpsideDownText(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public void onDraw(Canvas canvas) {
//This saves off the matrix that the canvas applies to draws, so it can be restored later.
canvas.save();
//now we change the matrix
//We need to rotate around the center of our text
//Otherwise it rotates around the origin, and that's bad.
float py = this.getHeight()/2.0f;
float px = this.getWidth()/2.0f;
canvas.rotate(180, px, py);
//draw the text with the matrix applied.
super.onDraw(canvas);
//restore the old matrix.
canvas.restore();
}
}
And this is my XML layout:
<bab.foo.UpsideDownText
android:text="Score: 0"
android:id="#+id/tvScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
>
</bab.foo.UpsideDownText>
in the xml file add:
android:rotation = "180"
in the respective element to display text upside down.
for example:
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="TextView"
android:rotation="180"/>
I haven't tried doing this myself, but I think it should work.
Override the view's onDraw method, call it's super passing in the canvas, then after call the rotate method on the canvas passed to it, passing in either 180 or Math.PI, depending on whether it works in degrees or radians.
the below code is working perfectly (thanks by the way). try to add the missing constructor. if it is not working my the problem is android version main is 2.1
package p.c;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
public class TextViewUD extends TextView {
public TextViewUD(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public TextViewUD(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public TextViewUD(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
float py = this.getHeight()/2.0f;
float px = this.getWidth()/2.0f;
Log.d("testUD", String.format("w: %d h: %d ", this.getWidth(), this.getHeight()));
Log.d("testUD", String.format("w: %f h: %f ", py, px));
canvas.rotate(180, px, py);
super.onDraw(canvas);
canvas.restore();
}
}