Android Custom View Animation - android

Hy, I'm trying to do an animation of a circle drawn on canvas. I can do that pretty easily with ObjectAnimator, however I'd like to start the animation when the view finishes loading or finishes drawing. If I start the animation in init(), the animation property will be "ahead" of the actual drawing, so I need to start it on a callback when the whole view is properly set up. I could do that onMeasure() or onSizeChanged() but those two get called too many times and if i have nested layouts it doesn't work properly. If I use startDelay() it works but I don't think that is an accurate procedure.
Here is a basic custom view class with animation property that changes the radius of a circle.
public class CustomView extends View {
private static final String TAG = CustomView.class.toString();
public CustomView(final Context context) {
super(context);
init(context);
}
public CustomView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(final Context context) {
// OUTER CIRCLE PAINT
mPaint = new Paint();
// Adds anti-aliasing to drawed elements
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
mPaint.setStrokeWidth(1);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStyle(Paint.Style.FILL);
final int animationTime = getResources().getInteger(ANIMATION_TIME_ID);
progressAnimator = ObjectAnimator.ofFloat(this, "animProgress", 0f, 0f);
progressAnimator.setDuration(animationTime);
Log.d(TAG, "Init ended");
//startAnimationCircle(50f);
}
#Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(this.getWidth()/2, this.getHeight()/2, animProgress, mPaint);
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
//startAnimationCircle(50f);
}
/**
* onMeasure() is called automatically right after a call to measure()
*/
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//startAnimationCircle(50f);
}
private Paint mPaint;
private static final int ANIMATION_TIME_ID = android.R.integer.config_mediumAnimTime;
private float animProgress;
private ObjectAnimator progressAnimator;
public float getAnimProgress() {
return animProgress;
}
public void setAnimProgress(float animProgress) {
this.animProgress = animProgress;
this.invalidate();
}
public void startAnimationCircle(float size) {
progressAnimator.setFloatValues(animProgress, size);
//progressAnimator.setStartDelay(2000);
progressAnimator.start();
}
}
And the XML also.
<com.your-package.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent" />

Related

How to add custom views in custom group view and be able to move them in android

I want to create a custom view that will act as a container for other custom views. The user will be able to move the view you want to move by dragging it.
For the container, I implement it by extending ViewGroup:
public class MyContainer extends ViewGroup {
public MyContainer(Context context) {
super(context);
init(context);
}
public MyContainer(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyContainer(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
public void addItem(AbstractView view) {
addView(view);
}
}
For the custom view, I create an AbstractView class and create two views RectView and CircleView by extending it:
abstract public class AbstractView extends View {
protected float mX, mY;
protected int mWidth, mHeight;
protected final Paint mShapePaint;
public AbstractView(Context context, float x, float y, int width, int height) {
super(context);
mShapePaint = new Paint();
mShapePaint.setColor(Color.BLACK);
mShapePaint.setStyle(Paint.Style.STROKE);
mX = x;
mY = y;
mWidth = width;
mHeight = height;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure the view
int width = resolveSize(mWidth, widthMeasureSpec);
int height = resolveSize(mHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Update the position of the item when it is moved
mX = event.getX();
mY = event.getY();
invalidate();
break;
}
return true;
}
}
public class CircleView extends AbstractView {
public CircleView(Context context, float x, float y) {
super(context, x, y, 100, 100);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawCircle(mX, mY, mWidth/2f, mShapePaint);
}
}
public class RectView extends AbstractView {
public RectView(Context context, float x, float y) {
super(context, x, y, 100, 40);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawRect(mX, mY, mX + mWidth, mY + mHeight, mShapePaint);
}
}
Here is the code for MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyContainer myContainer = findViewById(R.id.myContainer);
myContainer.addItem(new RectView(this, 50, 50));
myContainer.addItem(new CircleView(this, 200, 200));
}
}
Finally here is the xml file:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.my.package.name.MyContainer
android:id="#+id/myContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
When I run this code nothing visible on the screen, but when I extend FrameLayout instead of ViewGroup (as someone suggests) and remove onLayout method from the code, the view is now visible but I can only drag the last added view to this groupView (only able to move the topmost View). My question is: how can I make the views visible and be able to move them? (I think the problem is because I need to implement onMesure and onLayout for the groupView but I don't know how to implement them in this case) or at least How can I move the other views (not just the top most view) in the case of extending FrameLayout ?

Android extend EditText draw on canvas out of bounds

Im trying to draw text on EditText below by extending it and overriding onDraw function:
As you can see, the word gets cut off, from what am I seeing online, they don't do anything on the canvas aside from drawing on it. From what I've observed, I think because the canvas of the EditText is limited, that's why it is being cut off. I know there's a better solution rather than overriding onDraw, but I want to know the reason why this is happening. Can anybody explain or give a hint? Thank you very much.
CustomEditText.java:
public class CustomEditText extends AppCompatEditText {
private Rect mTitleRect;
private Rect mErrorTextRect;
private Paint mTitlePaint;
private Paint mErrorTextPaint;
private String mTitle = "";
private String mErrorText = "";
private int mEditTextHeight;
public CustomEditText(Context context) {
super(context);
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.customEditTextStyle);
init();
init(context, attrs);
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
init(context, attrs);
}
private void init() {
mTitleRect = new Rect();
mErrorTextRect = new Rect();
mTitlePaint = new Paint();
mErrorTextPaint = new Paint();
mTitlePaint.setColor(Color.BLACK);
mTitlePaint.setTextSize(getResources().getDimension(R.dimen.text_small));
mErrorTextPaint.setColor(Color.parseColor("#FF4336"));
mErrorTextPaint.setTextSize(getResources().getDimension(R.dimen.text_small));
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomEditText);
try {
mTitle = a.getString(R.styleable.CustomEditText_headerTitle);
mErrorText = a.getString(R.styleable.CustomEditText_errorText);
} finally {
a.recycle();
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mEditTextHeight = h;
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mTitle != null && !mTitle.isEmpty()) {
mTitlePaint.getTextBounds(mTitle, 0, mTitle.length(), mTitleRect);
canvas.drawText(mTitle, getPaddingLeft(), getPaddingTop() - mTitleRect.height(), mTitlePaint);
}
if (mErrorText != null && !mErrorText.isEmpty()) {
mErrorTextPaint.getTextBounds(mErrorText, 0, mErrorText.length(), mErrorTextRect);
canvas.drawText(mErrorText, getPaddingLeft(), mEditTextHeight + mErrorTextRect.height() / 2, mErrorTextPaint);
}
}
}
attrs.xml
<declare-styleable name="CustomEditText">
<attr name="errorText" format="string|reference" />
<attr name="headerTitle" format="string|reference" />
</declare-styleable>
XML:
<com.mypackage.CustomEditText
android:id="#+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
app:errorText="errorrrr"
app:headerTitle="testing title" />
I think you are misinterpreting android canvas coordinate. Origin coordinate (0, 0) of a canvas is at the very left top x-coordinate is increasing as you go to the right and y-coordinate is increasing as you go to the bottom.
You need to pass left top coordinate of the text that you want to draw.
https://developer.android.com/reference/android/graphics/Canvas#drawText(java.lang.String,%20float,%20float,%20android.graphics.Paint)
I could not understand where do you want to draw the the text so assuming that you want to draw at the top left of the view have to call draw text like this
canvas.drawText(mTitle, getPaddingLeft(), getPaddingTop(), mTitlePaint);
public class CustomEditText extends AppCompatEditText {
private Rect mTitleRect;
private Rect mErrorTextRect;
private Paint mTitlePaint;
private Paint mErrorTextPaint;
private String mTitle = "";
private String mErrorText = "";
private int mEditTextHeight;
public CustomEditText(Context context) {
this(context, null);
}
public CustomEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
init(attrs);
}
private void init() {
mTitleRect = new Rect();
mErrorTextRect = new Rect();
mTitlePaint = new Paint();
mErrorTextPaint = new Paint();
mTitlePaint.setColor(Color.BLACK);
mTitlePaint.setTextSize(getResources().getDimension(R.dimen.text_small));
mErrorTextPaint.setColor(Color.parseColor("#FF4336"));
mErrorTextPaint.setTextSize(getResources().getDimension(R.dimen.text_small));
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomEditText);
try {
mTitle = a.getString(R.styleable.CustomEditText_headerTitle);
mErrorText = a.getString(R.styleable.CustomEditText_errorText);
} finally {
a.recycle();
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mEditTextHeight = h;
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.text_small));
if (mTitle != null && !mTitle.isEmpty()) {
mTitlePaint.getTextBounds(mTitle, 0, mTitle.length(), mTitleRect);
canvas.drawText(mTitle, getPaddingLeft(), getPaddingTop() - mTitleRect.height(), mTitlePaint);
}
if (mErrorText != null && !mErrorText.isEmpty()) {
mErrorTextPaint.getTextBounds(mErrorText, 0, mErrorText.length(), mErrorTextRect);
canvas.drawText(mErrorText, getPaddingLeft(), getHeight(), mErrorTextPaint);
}
}

Custom view to mask a parent and make transparent circle at center of parent

I want to make a circular suface view (porthole effect). Surface view is inside a Frame layout. I want to make a custom view that i can add to Frame layout on top of surface view and mask whole Frame layout to produce porthole effect so that surface view will be shown as circle.
I searched and a lot for answer on Web and Stackoverflow but failed.
Then i saw this question and i tried this custom view to mask frame layout(and hence surfaceview) but i am not getting the desired result.
What i want is a custom view that can take height and width of it's parent (parent is square in shape) and make a transparent circle at it's center touching all four sides at middle of the boundaries, rest(view - circle) of the view will be of color that i can set.
public class FocusView extends View {
private Paint mTransparentPaint;
private Paint mSemiBlackPaint;
private Path mPath = new Path();
public static float radius , xCor , yCor;
public FocusView(Context context) {
super(context);
initPaints();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaints();
}
public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
private void initPaints() {
mTransparentPaint = new Paint();
mTransparentPaint.setColor(Color.GREEN);
mTransparentPaint.setStrokeWidth(10);
mSemiBlackPaint = new Paint();
mSemiBlackPaint.setColor(Color.TRANSPARENT);
mSemiBlackPaint.setStrokeWidth(10);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.addCircle(xCor,yCor,radius, Path.Direction.CW);
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
canvas.drawCircle(xCor,yCor,radius, mTransparentPaint);
canvas.drawPath(mPath, mSemiBlackPaint);
canvas.clipPath(mPath);
canvas.drawColor(Color.parseColor("#FFFFFF")); //A6000000
}
}
Please if somebody can help me. Thanks in advance.
This is an example of a view that paints the whole view pink and cuts a centered, circular hole making the parent visible:
public class FocusView extends View {
private Paint mCutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap mBitmap;
private Canvas mInternalCanvas;
public FocusView(Context context) {
super(context);
init();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FocusView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mCutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mInternalCanvas != null) {
mInternalCanvas.setBitmap(null);
mInternalCanvas = null;
}
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mInternalCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
if (mInternalCanvas == null || mBitmap == null) {
return;
}
final int width = getWidth();
final int height = getHeight();
// make the radius as large as possible within the view bounds
final int radius = Math.min(width, height) / 2;
mInternalCanvas.drawColor(0xFFFF00FF);
mInternalCanvas.drawCircle(width / 2, height / 2, radius, mCutPaint);
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
The reason for drawing to an internal Bitmap first is that if you apply PorterDuff.Mode.CLEAR to the original Canvas it will cut away everything that's been previously drawn to the canvas, including the parent view.
There may be better solutions out there, but this one is simple enough to understand.

ObjectAnimator inside thread

I am quite new to animations and was trying to implement objectAnimator inside thread.The animation is to create a blinking effect (Like RippleEffect) which is in infinite loop.
private void hidePressedRing() {
pressedAnimator.setFloatValues(pressedRingWidth, 0f);
pressedAnimator.start();
}
private void showPressedRing() {
pressedAnimator.setFloatValues(animationProgress, pressedRingWidth);
pressedAnimator.start();
}
The below snippet is inside a thread handler() inside run() method.
if (pressed) {
showPressedRing();
pressed=false;
} else {
hidePressedRing();
pressed=true;
}
how should i implement blinking effect on a circle using objectAnimator in a loop;
Change below code according to your requirement...
public class ProgressCircle extends View {
private Paint paint;
private int width;
private int height;
private int radius;
private int cx;
private int cy;
private float tempRadius;
public ProgressCircle(Context context) {
super(context);
init();
}
public ProgressCircle(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ProgressCircle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = right - left;
height = bottom - top;
cx=width/2;
cy=height/2;
radius=Math.min(width,height)/2;
}
private Runnable runnable=new Runnable() {
#Override
public void run() {
tempRadius++;
if(tempRadius==radius){
tempRadius=0;
}
invalidate();
handler.postDelayed(this,50);
}
};
private Handler handler=new Handler();
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
handler.post(runnable);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeCallbacks(runnable);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(cx,cy,tempRadius,paint);
}
}

Draw circle on canvas simulating animation android

I need to draw an empty circle with a margin of 10 px. The problem that i've encountered is that i need to simulate the draw of the circle in 2 seconds and after that to start drawing on the top of it another one with another color. I'm using a custom view and i've tried to implement my logic into onDraw method and invalidate the view every 50 milisecond. The problem is that i can't manage to draw the circle...i draw only crapy figures. Does somebody know how can i draw a circle without using the canvas.drawCircle method because that method is drawing the circle directly without animation.
My current code
public class CustomAnimationView extends View{
private Canvas canvas;
private int count = 0;
private Paint paint;
private int mLeft;
private int mRight;
private int mBottom;
private int mTop;
public CustomAnimationView(Context context) {
super(context);
}
public CustomAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomAnimationView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAttributes(attrs);
}
private void setAttributes(AttributeSet attrs) {
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
if(paint == null){
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(10);
paint.setColor(Color.BLACK);
}
if(count<150){
drawFirstQuarter(count);
}
count++;
}
public void drawFirstQuarter(int count){
RectF oval = new RectF(mLeft, mTop, mRight, mBottom);
canvas.drawArc(oval, 90, 30, true, paint);
}
public void setRect(int top, int bottom, int left, int right){
mBottom = bottom;
mTop = top;
mLeft = left;
mRight = right;
}
}
Right now I'm just tring to draw a bit of a circle.
Thanks. I've solved it.
Here is a code sample
public class CustomAnimationView extends View{
private Canvas canvas;
private int mCount = 0;
private Paint paint1;
private Paint paint2;
private RectF oval1;
private Context context;
private int mColorCount = 0;
public CustomAnimationView(Context context) {
super(context);
this.context = context;
}
public CustomAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public CustomAnimationView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
setAttributes(attrs);
}
private void setAttributes(AttributeSet attrs) {
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
if(paint1 == null){
paint1 = new Paint();
paint1.setAntiAlias(true);
paint1.setStyle(Style.STROKE);
paint1.setStrokeWidth(10);
}
if(paint2 == null){
paint2 = new Paint();
paint2.setAntiAlias(true);
paint2.setStyle(Style.STROKE);
paint2.setStrokeWidth(10);
}
if(mCount % 360 == 0 ){
mColorCount++;
}
if(mColorCount % 2 == 0){
paint1.setColor(context.getResources().getColor(R.color.white));
paint2.setColor(context.getResources().getColor(R.color.black));
}else{
paint2.setColor(context.getResources().getColor(R.color.white));
paint1.setColor(context.getResources().getColor(R.color.black));
}
if(oval1 == null)
oval1 = new RectF(5,5,canvas.getWidth()-5, canvas.getHeight()-5);
drawFirstQuarter(mCount, oval1);
}
public void drawFirstQuarter(int count, RectF oval){
canvas.drawArc(oval, 90, 360, false, paint2);
canvas.drawArc(oval, 90, count, false, paint1);
if(mCount == 330)
mCount = 0;
else
mCount += 30;
}
}

Categories

Resources