Im using the following code to animate the view when it is drawn...
public class MyView extends View {
int iCurStep = 0;// current animation step
class Points {
float x, y;
Points(float _x, float _y) {
x = _x;
y = _y;
}
}
Points[] drawPaths = {new Points(-75, 0), new Points(20, 60), new Points(60, 20)};
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
PathMeasure pm = new PathMeasure(path, false);
if (iCurStep <= 20) {
pm.getPosTan(fSegmentLen * iCurStep, afP, null);
path.moveTo(drawPaths[0].x, drawPaths[0].y);
for (int i = 1; i < drawPaths.length; i++) {
path.lineTo(drawPaths[i].y, drawPaths[i].y);
}
canvas.drawPath(path, paint);
iCurStep++;
} else {
iCurStep = 0;
}
}
}
What Im expecting is that, it has to be a growing view...I mean the view has to grow as it is being drawn...But Im not able to produce to do so...How can I be able to sort this out?
Use a ObjectAnimator.
Create your view as normal, but then create an ObjectAnimator (docs: https://developer.android.com/reference/android/animation/ObjectAnimator.html). You'll do something like this in your fragment or activity (you can make it in XML as well). Essentially you create an ObjectAnimator for an object with a property string. The property string must have a camelCase setter. So if you wanted to call view.setScaleX() as the property you were going to modify, you'd need to set the property string to 'scaleX'. Here's a simple example.
View growMe = new View(args);
float startSize = 1.0f;
float endSize = 2.0f;
ObjectAnimator grower = ObjectAnimator.ofFloat(growMe, "scaleX", startSize, endSize);
int durationMs = 1000;
grower.setDuration (durationMs)
grower.start();
The one other thing you'll need to do is add an AnimationListener to your View that actually adjusts the size to be the correct as the end of the animation as (I believe--I might be wrong on this part), the view will re-size after the animation ends.
I hope that helps!
Related
I am new to android & java. This might be very simple but I'm stuck... I am creating an app where by clicking on an "add" button a square is added in some default position on a canvas. You can then move and resize the square. Once you finish moving/ reshaping, my intention is for the square to maintain its new size/ position and for the user to be able to click on the add button where a new square will appear in the original default position. You can then again move / reshape the second square. And eventually add as many squares as you like (each unique).
I have managed to add the first square and i am also able to move/ reshape. I also created an array list to store the squares. My issue is that as soon as i add the second square, the shape and position of the first square are reset to the default position and size. And if i move the second square, so does the first one. So effectively its as if i end up with a stacked set of squares that all move and reshape at the same time.
The question is, how do i convert the variable information of position/ size to a constant before i add the square to the array?
I have the following in my main activity
addBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(addTimer != null) {
addTimer.cancel();
}
if (addclickcount>0) {
addBoxtoList();
}
addBox();
addclickcount++;
}
});
public void addBox(){ mCustomView.drawRoundRect();}
public void addBoxtoList(){mCustomView.addBoxtoList();}
Then in CustomView
public class CustomView extends View {
private ArrayList<Box> boxesArray = new ArrayList<>();
private RectF mRectSquare;
private Paint mPaintSquare;
private static RectF aRectSquare;
private static Paint aPaintSquare;
private int mSquareLeft, mSquareTop, dx, dy;
int colorSelected;
public CustomView(Context context) {
super(context);
init(null);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init (#Nullable AttributeSet set){
mRectSquare = new RectF();
mPaintSquare = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintSquare.setColor(Color.BLUE);
mPaintSquare.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintSquare.setAlpha(85);
}
public void drawRoundRect(){
if (General.addBoxCheck == true) {
dx = 200;
dy = 200;
mSquareLeft = scrwd - dx - 50;
mSquareTop = 50;
General.addBoxCheck = false;
}
mRectSquare.left = mSquareLeft;
mRectSquare.top = mSquareTop;
mRectSquare.right = mRectSquare.left + dx;
mRectSquare.bottom = mRectSquare.top + dy;
postInvalidate();
}
public void addBoxtoList(){
boxid = boxesArray.size() + 1;
aRectSquare = mRectSquare;
aPaintSquare = mPaintSquare;
Box boxa = new Box(boxid, aRectSquare, aPaintSquare);
boxesArray.add(boxa);
//postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRoundRect(mRectSquare, 15, 15, mPaintSquare);
if(boxesArray != null) {
RectF rectF = new RectF();
Paint paint = new Paint();
for (int i = 0; i < boxesArray.size(); i++) {
paint = boxesArray.get(i).getPaint();
rectF = boxesArray.get(i).getRect();
canvas.drawRoundRect(rectF, 15, 15, paint);
}
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean value = super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
return true;
}
case MotionEvent.ACTION_MOVE:{
int x = (int) event.getX();
int y = (int) event.getY();
mSquareLeft = x;
mSquareTop = y;
postInvalidate();
return value;
}
}
return value;
}
Thanks
The question seems quite vague, if the values are changing then it means that you changed the values. If you don’t change the values of the array then essentially it should be a "constant". My assumption is that you are overwriting the array and therefore the value is changing. It would help to see your code.
Check the index of the stored square shapes and verify that you're not overwriting on any of the previous indexes.
I'm developing a game which shows shapes randomly, I got the class but I got no idea how to call the custom view from my GameFragment. I was wondering if anyone can help me to achieve it.
CustomView.java
public class CustomViews extends View {
private final int SQUARE_SIZE = 500;
private RectF rectF;
private Paint paint;
private boolean CIRCLE = false;
private boolean RECTANGLE = false;
public CustomViews(Context context) {
super(context);
init(null);
}
public CustomViews(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomViews(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomViews(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(#Nullable AttributeSet set) {
rectF = new RectF();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
randomColorGenerator();
}
public void rect(Canvas canvas) {
int height = canvas.getHeight() / 2;
int width = canvas.getWidth() / 2;
rectF.set(width - 200, height - 200, width + 200, height + 200);
}
private void circle(Canvas canvas) {
float cx;
float cy;
float radius = 200;
cx = canvas.getWidth() / 2;
cy = canvas.getWidth() / 2;
canvas.drawCircle(cx, cy, radius, paint);
}
private void randomColorGenerator() {
Random random = new Random();
int choices = 3;
switch (random.nextInt(choices)) {
case 0:
paint.setColor(Color.GREEN);
break;
case 1:
paint.setColor(Color.RED);
break;
case 2:
paint.setColor(Color.BLACK);
break;
}
}
public void randomShapeGenerator(Canvas canvas) {
Random random = new Random();
int numberOfMethods = 2;
switch (random.nextInt(numberOfMethods)) {
case 0:
rect(canvas);
rectSelected();
break;
case 1:
circle(canvas);
circleSelected();
break;
}
}
public boolean circleSelected(){
RECTANGLE = false;
return CIRCLE = true;
}
public boolean rectSelected( ) {
CIRCLE = false;
return RECTANGLE = true;
}
#Override
public void onDraw(Canvas canvas) {
randomShapeGenerator(canvas);
canvas.drawRect(rectF, paint);
}
}
Now I need to call it from my GameFragment where there are some buttons and whenever you click on them a new random shape must be generate. Is anyone got any idea?
Assuming your fragment has a LinearLayout with id main_content then this might help you to add your custom view programmatically:
Button btn = findViewById(R.id.yourbutton);
btn.setOnClickListener(v -> {
LinearLayout main_layout = findViewById(R.id.main_content);
CustomViews customView = new CustomViews(this);
customView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
main_layout.addView(customView);
});
layoutParams might be also tuned according your needs.
You have to take into account where you want to display the customViews and also their sizes. For instance, if you have a linear layout with vertical orientation, you can add views in the following way:
main_layout.addView(customView, 400, 400);
This will display multiple customViews one below the other (until the screen's height is reached).
Indeed, CustomViews has to be modified a bit (here are two methods to be changed):
public void rect(Canvas canvas) {
int height = canvas.getHeight() / 2;
int width = canvas.getWidth() / 2;
rectF.set(width - 200, height - 200, width + 200, height + 200);
canvas.drawRect(rectF, paint);
}
#Override
public void onDraw(Canvas canvas) {
randomShapeGenerator(canvas);
}
I am trying to create a custom view that has a Circle and in it, I have to have sections in run time as shown in the image below. I tried a lot of stuff in onDraw method but got no luck. I even tried https://github.com/donvigo/CustomProgressControls . Basically, I want to give a number of sections and then in each section I can select colors as per my need.
I am looking for ProgressBar that should have gap/space as shown in the image; in between circles. Say if I have given 5 sections, 3 of which should be "full", it should color the first 3 in red, and the other 2 in green, for example.
To draw I am doing like:
private void initExternalCirclePainter() {
internalCirclePaint = new Paint();
internalCirclePaint.setAntiAlias(true);
internalCirclePaint.setStrokeWidth(internalStrokeWidth);
internalCirclePaint.setColor(color);
internalCirclePaint.setStyle(Paint.Style.STROKE);
internalCirclePaint.setPathEffect(new DashPathEffect(new float[]{dashWith, dashSpace}, dashSpace));
}
I might be a little late to the party, but I actually wrote a custom component that has 2 rings that look quite similar to what you're trying to achieve. You can just remove the outer ring easily. The image of what I got in the end:
Here's the class:
public class RoundedSectionProgressBar extends View {
// The amount of degrees that we wanna reserve for the divider between 2 sections
private static final float DIVIDER_ANGLE = 7;
public static final float DEGREES_IN_CIRCLE = 360;
public static final int PADDING = 18;
public static final int PADDING2 = 12;
protected final Paint paint = new Paint();
protected final Paint waitingPaint = new Paint();
protected final Paint backgroundPaint = new Paint();
private int totalSections = 5;
private int fullSections = 2;
private int waiting = 3; // The outer ring. You can omit this
private RectF rect = new RectF();
public RoundedSectionProgressBar(Context context) {
super(context);
init(context, null);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// Can come from attrs if need be?
int strokeWidth = 3;
setupPaint(context, strokeWidth, paint, R.color.filled_color_inner_ring);
setupPaint(context, strokeWidth, waitingPaint, R.color.empty_color_inner_ring);
setupPaint(context, strokeWidth, backgroundPaint, R.color.filled_color_outer_ring);
}
private void setupPaint(Context context, int strokeWidth, Paint backgroundPaint, int colorRes) {
backgroundPaint.setStrokeCap(Paint.Cap.SQUARE);
backgroundPaint.setColor(context.getResources().getColor(colorRes));
backgroundPaint.setAntiAlias(true);
backgroundPaint.setStrokeWidth(strokeWidth);
backgroundPaint.setStyle(Paint.Style.STROKE);
}
public int getTotalSections() {
return totalSections;
}
public void setTotalSections(int totalSections) {
this.totalSections = totalSections;
invalidate();
}
public int getFullSections() {
return fullSections;
}
public void setNumberOfSections(int fullSections, int totalSections, int waiting) {
this.fullSections = fullSections;
this.totalSections = totalSections;
this.waiting = waiting;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(getLeft() + PADDING, getTop() + PADDING, getRight() - PADDING, getBottom() - PADDING);
float angleOfSection = (DEGREES_IN_CIRCLE / totalSections) - DIVIDER_ANGLE;
// Drawing the inner ring
for (int i = 0; i < totalSections; i++) {
// -90 because it doesn't start at the top, so rotate by -90
// divider_angle/2 especially in 2 sections, it's visibly rotated by Divider angle, so we split this between last and first
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
if (i < fullSections) {
canvas.drawArc(rect, startAngle, angleOfSection, false, paint);
} else {
canvas.drawArc(rect, startAngle, angleOfSection, false, backgroundPaint);
}
}
// Drawing the outer ring
rect.set(getLeft() + PADDING2, getTop() + PADDING2, getRight() - PADDING2, getBottom() - PADDING2);
for (int i = 0; i < waiting; i++) {
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
canvas.drawArc(rect, startAngle, angleOfSection, false, waitingPaint);
}
}
}
Notice that this code won't give you the outer ring's 'empty' slots, since we decided against them in the end. The inner circle will have both the empty and filled slots. The whole class can be reused, and it's responsible just for the 2 rings that are drawn, the 6/6, +3 and the red circle are parts of another view.
The most important piece of the code is the onDraw method. It contains the logic for drawing the arcs in the for loop, as well as the logic for calculating the angles and adding spaces between them. Everything is rotated by -90 degrees, because I needed it to start at the top, rather than on the right, as it is the 0-degree angle in Android. It's not that complex, and you can modify it to fit your needs better should you need to.
I find it easier to do math for drawArc(operating on angle values based on number of sections) rather than computing the arc length.
Here's a quick idea, with a lot of hard-coded properties, but you should be able to get the idea:
public class MyStrokeCircleView extends View {
private Paint mPaint;
private RectF mRect;
private int mPadding;
private int mSections;
private int mFullArcSliceLength;
private int mColorArcLineLength;
private int mArcSectionGap;
public MyStrokeCircleView(Context context) {
super(context);
init(null, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mPaint = new Paint();
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.darker_gray));
mPadding = 5;
mRect = new RectF(mPadding, mPadding, mPadding, mPadding);
mSections = 4;
mFullArcSliceLength = 360 / mSections;
mArcSectionGap = mFullArcSliceLength / 10;
mColorArcLineLength = mFullArcSliceLength - 2 * mArcSectionGap;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRect.right = getWidth() - mPadding;
mRect.bottom = getHeight() - mPadding;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mSections; i++) {
canvas.drawArc(mRect, i * mFullArcSliceLength + mArcSectionGap, mColorArcLineLength, false, mPaint);
}
}
}
I have a problem with my very simple custom view. Its intention is only to draw simple vertical dashed line. I would like to change the color of the line according to the pressed state of its parent container. I have this code:
public class DottedLine extends View {
float density ;
float size;
Paint paint;
public DottedLine(Context context) {
this(context, null, 0);
}
public DottedLine(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DottedLine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
density = metrics.density;
size = 2 * density; //2dp
paint = new Paint();
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(size);
paint.setColor(getResources().getColor(R.color.main_kosapp));
paint.setPathEffect(new DashPathEffect(new float[] {size, size}, 0));
}
#Override
protected void onDraw(Canvas canvas) {
float diff = canvas.getHeight()%size;
Path path = new Path();
path.moveTo(canvas.getWidth()/2, diff/2);
path.lineTo(canvas.getWidth() / 2,canvas.getHeight()-diff/2);
if(this.isPressed() || this.isFocused()) {
paint.setColor(getResources().getColor(R.color.light_gray));
} else {
paint.setColor(getResources().getColor(R.color.main_kosapp));
}
canvas.drawPath(path, paint);
}
}
The problem is, that the onDraw method gets not called after I press the view. I tried to set duplicateParentState to true, but it did not help at all. FYI in my layout this view has two direct siblings - textviews - which both have its text color defined with selectors and it works for those textviews.
What is wrong with my view implementation? What do I need to add to the class to make selectors working?
you should invalidate your view for the pressed state by overriding the dispatchSetPressed
#Override
protected void dispatchSetPressed(boolean pressed) {
super.dispatchSetPressed(pressed);
invalidate();
}
at least that worked for me without using the motion events
I'd like to have an animated image in my activity:
just a white circle moving on a trajectory (black line).
What is the best way to do it?
Translate animation
FrameAnimation
Canvas
The implementation could be:
The white circle is a small ImageView with transparent background. It is placed on top of another ImageView (black curve).
FrameAnimation: For each position of the circle there is a separate png-Image of the whole screen, which is a frame of the animation.
Use drawCircle(), and restoreBackgroundImage() for each movement of the white dot.
So far I tried a FrameAnimation, but I get outOfMemoryError for just 10 Frames.
The following code implements the Canvas way. Efficient and no OOM. You just need to change your trajectory into a Path object.
public class TestView extends View {
private Path path;
private Paint pathPaint;
private Paint dotPaint;
private long beginTime;
private long duration = 3000;
private float dotRadius = 3;
private PathMeasure pm;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
path = new Path();
path.moveTo(0, 100);
path.lineTo(100, 200);
path.lineTo(200, 50);
//TODO: Put your path here
pm = new PathMeasure(path, false);
pathPaint = new Paint();
pathPaint.setARGB(255, 0, 0, 0);
pathPaint.setStrokeWidth(2);
pathPaint.setStyle(Paint.Style.STROKE);
dotPaint = new Paint();
dotPaint.setARGB(255, 255, 255, 255);
dotPaint.setStyle(Paint.Style.FILL);
beginTime = 0;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(0, 0, 0, 0);
canvas.drawPath(path, pathPaint);
long currentTime = System.currentTimeMillis();
float currentDistance;
if (beginTime == 0) {
beginTime = currentTime;
currentDistance = 0;
} else if (beginTime > 0 && currentTime - beginTime < duration) {
currentDistance = (float) (currentTime - beginTime) / (float) duration * pm.getLength();
} else {
beginTime = -1;
return;
}
float pos[] = new float[2];
pm.getPosTan(currentDistance, pos, null);
canvas.drawCircle(pos[0], pos[1], dotRadius, dotPaint);
invalidate();
}
}