Custom instruction overlay with highlighted view (without using ShowcaseViewLibrary) - android

I would like to know if there is any simple solution to creating an overlay where an element would get highlighted.
So the final result would look something like this:
I would like to avoid using ShowcaseViewLibrary from variety of reason (it doesn't have the look I need, it's no longer supported etc.).
I thought about using FrameLayout but I am not sure how to achieve the highlighted existing element. Also putting the arrows or bubbles to the elements so they connect precisely.

A quick and easy way would be to make a copy of the Activity you want to demonstrate with overlays added and just show that. It's what I do and it works fine.

/**
* Created by Nikola D. on 10/1/2015.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ShowCaseLayout extends ScrimInsetsFrameLayout {
private static final long DEFAULT_DURATION = 1000;
private static final int DEFAULT_RADIUS = 100;
private Paint mEmptyPaint;
private AbstractQueue<Pair<String, View>> mTargetQueue;
private int mLastCenterX = 600;
private int mLastCenterY = 100;
private ValueAnimator.AnimatorUpdateListener mAnimatorListenerX = new ValueAnimator.AnimatorUpdateListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mLastCenterX = (int) animation.getAnimatedValue();
setWillNotDraw(false);
postInvalidate();
}
};
private ValueAnimator.AnimatorUpdateListener mAnimatorListenerY = new ValueAnimator.AnimatorUpdateListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mLastCenterY = (int) animation.getAnimatedValue();
setWillNotDraw(false);
postInvalidate();
}
};
private ValueAnimator mCenterAnimatorX;
private ValueAnimator mCenterAnimatorY;
private boolean canRender = false;
private OnAttachStateChangeListener mAttachListener = new OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View v) {
canRender = true;
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
#Override
public void onViewDetachedFromWindow(View v) {
canRender = false;
removeOnAttachStateChangeListener(this);
}
};
private long mDuration = DEFAULT_DURATION;
private int mRadius = (int) DEFAULT_RADIUS;
private Interpolator mInterpolator = new LinearOutSlowInInterpolator();
private ValueAnimator mRadiusAnimator;
private ValueAnimator.AnimatorUpdateListener mRadiusAnimatorListener = new ValueAnimator.AnimatorUpdateListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mRadius = (int) animation.getAnimatedValue();
}
};
private TextView mDescriptionText;
private Button mGotItButton;
private OnClickListener mExternalGotItButtonlistener;
private OnClickListener mGotItButtonClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
setNextTarget();
if (mExternalGotItButtonlistener != null) {
mExternalGotItButtonlistener.onClick(v);
}
}
};
private Animator.AnimatorListener mAnimatorSetListener = new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setNextTarget();
invalidate();
//mDescriptionText.layout(mTempRect.left, mTempRect.bottom + mTempRect.bottom, mDescriptionText. );
}
};
private Rect mTempRect;
private Paint mBackgroundPaint;
private Bitmap bitmap;
private Canvas temp;
private int mStatusBarHeight = 0;
public ShowCaseLayout(Context context) {
super(context);
setupLayout();
}
public ShowCaseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setupLayout();
}
public ShowCaseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupLayout();
}
public void setTarget(View target, String hint) {
mTargetQueue.add(new Pair<>(hint, target));
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setupLayout() {
mTargetQueue = new LinkedBlockingQueue<>();
setWillNotDraw(false);
mBackgroundPaint = new Paint();
int c = Color.argb(127, Color.red(Color.RED), Color.blue(Color.RED), Color.green(Color.RED));
mBackgroundPaint.setColor(c);
mEmptyPaint = new Paint();
mEmptyPaint.setColor(Color.TRANSPARENT);
mEmptyPaint.setStyle(Paint.Style.FILL);
mEmptyPaint.setAntiAlias(true);
mEmptyPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
if (!ViewCompat.isLaidOut(this))
addOnAttachStateChangeListener(mAttachListener);
else canRender = true;
mDescriptionText = new TextView(getContext());
mGotItButton = new Button(getContext());
mGotItButton.setText("GOT IT");
mGotItButton.setOnClickListener(mGotItButtonClickListener);
addView(mGotItButton, generateDefaultLayoutParams());
//ViewCompat.setAlpha(this, 0.5f);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!canRender) return;
temp.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
temp.drawCircle(mLastCenterX, mLastCenterY, mRadius, mEmptyPaint);
canvas.drawBitmap(bitmap, 0, 0, null);
}
#TargetApi(Build.VERSION_CODES.M)
private void animateCenterToNextTarget(View target) {
int[] locations = new int[2];
target.getLocationInWindow(locations);
int x = locations[0];
int y = locations[1];
mTempRect = new Rect(x, y, x + target.getWidth(), y + target.getHeight());
int centerX = mTempRect.centerX();
int centerY = mTempRect.centerY();
int targetRadius = Math.abs(mTempRect.right - mTempRect.left) / 2;
targetRadius += targetRadius * 0.05;
mCenterAnimatorX = ValueAnimator.ofInt(mLastCenterX, centerX).setDuration(mDuration);
mCenterAnimatorX.addUpdateListener(mAnimatorListenerX);
mCenterAnimatorY = ValueAnimator.ofInt(mLastCenterY, centerY).setDuration(mDuration);
mCenterAnimatorY.addUpdateListener(mAnimatorListenerY);
mRadiusAnimator = ValueAnimator.ofInt(mRadius, targetRadius);
mRadiusAnimator.addUpdateListener(mRadiusAnimatorListener);
playTogether(mCenterAnimatorY, mCenterAnimatorX, mRadiusAnimator);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.TRANSPARENT);
temp = new Canvas(bitmap);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void playTogether(ValueAnimator... animators) {
AnimatorSet set = new AnimatorSet();
set.setInterpolator(mInterpolator);
set.setDuration(mDuration);
set.playTogether(animators);
set.addListener(mAnimatorSetListener);
set.start();
}
public void start(Activity activity) {
if (getParent() == null) {
attachLayoutToWindow(activity);
}
setNextTarget();
}
private void setNextTarget() {
Pair<String, View> pair = mTargetQueue.poll();
if (pair != null) {
if (pair.second != null)
animateCenterToNextTarget(pair.second);
mDescriptionText.setText(pair.first);
}
}
private void attachLayoutToWindow(Activity activity) {
FrameLayout rootLayout = (FrameLayout) activity.findViewById(android.R.id.content);
rootLayout.addView(this);
}
public void hideShowcaseLayout() {
}
public void setGotItButtonClickistener(OnClickListener mExternalGotItButtonlistener) {
this.mExternalGotItButtonlistener = mExternalGotItButtonlistener;
}
public TextView getDescriptionTextView() {
return mDescriptionText;
}
public void setDescriptionTextView(TextView textView) {
mDescriptionText = textView;
}
}
Please note that this code is incomplete and is under development, you should tweak it according your needs.
This layout will draw a circle around the View over its Rect.
Instead of drawing the circle you could drawRect to the Rect bounds of the target view or drawRoundRect if the View's Rect and background drawable Rect are complementary.
Drawing the line (drawLine()) should be from the target view:
startX = (rect.right - rect.left)/2;
startY = rect.bottom;
endX = startX;
endY = startY + arbitraryLineHeight;
if the endY is larger than the layout height you should be drawing it upwards rect.top - arbitraryLineHeight, otherwise you draw it as it is.
arbitraryLineHeight could be descriptionViewRect.top which makes it more dynamic, instead of using a constant value.

Related

android.graphics draw a line from one View pointing to another View

I know android.graphics is old, but i am having trouble doing a simple stuff.
I want to draw a line animation where one View points an arrow/line into another View
First Button-------------------------------->Second Button
I have tried creating a custom View class and overriding the onDraw(Canvas c) method and then using the drawLine(startX, startY, stopX, stopY, paint) method from the Canvas Object. But i don't know which coordinates to get in order to point one View to the other View
I don't want to create a static View in the XML layout with a slim height because the View can be added dynamically by the user, which i think drawing the line dynamically is the best way.
Please help me out. Thank you!
For drawing lines between views better if all of it lays on same parent layout. For the conditions of the question (Second Button is exactly to the right of First Button) you can use custom layout like that:
public class ArrowLayout extends RelativeLayout {
public static final String PROPERTY_X = "PROPERTY_X";
public static final String PROPERTY_Y = "PROPERTY_Y";
private final static double ARROW_ANGLE = Math.PI / 6;
private final static double ARROW_SIZE = 50;
private Paint mPaint;
private boolean mDrawArrow = false;
private Point mPointFrom = new Point(); // current (during animation) arrow start point
private Point mPointTo = new Point(); // current (during animation) arrow end point
public ArrowLayout(Context context) {
super(context);
init();
}
public ArrowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setWillNotDraw(false);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(5);
}
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
if (mDrawArrow) {
drawArrowLines(mPointFrom, mPointTo, canvas);
}
canvas.restore();
}
private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {
Point pointFrom = new Point();
pointFrom.x = fromViewBounds.right;
pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;
return pointFrom;
}
private Point calcPointTo(Rect fromViewBounds, Rect toViewBounds) {
Point pointTo = new Point();
pointTo.x = toViewBounds.left;
pointTo.y = toViewBounds.top + (toViewBounds.bottom - toViewBounds.top) / 2;
return pointTo;
}
private void drawArrowLines(Point pointFrom, Point pointTo, Canvas canvas) {
canvas.drawLine(pointFrom.x, pointFrom.y, pointTo.x, pointTo.y, mPaint);
double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);
int arrowX, arrowY;
arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle + ARROW_ANGLE));
arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle + ARROW_ANGLE));
canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);
arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle - ARROW_ANGLE));
arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle - ARROW_ANGLE));
canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);
}
public void animateArrows(int duration) {
mDrawArrow = true;
View fromView = getChildAt(0);
View toView = getChildAt(1);
// find from and to views bounds
Rect fromViewBounds = new Rect();
fromView.getDrawingRect(fromViewBounds);
offsetDescendantRectToMyCoords(fromView, fromViewBounds);
Rect toViewBounds = new Rect();
toView.getDrawingRect(toViewBounds);
offsetDescendantRectToMyCoords(toView, toViewBounds);
// calculate arrow sbegin and end points
Point pointFrom = calcPointFrom(fromViewBounds, toViewBounds);
Point pointTo = calcPointTo(fromViewBounds, toViewBounds);
ValueAnimator arrowAnimator = createArrowAnimator(pointFrom, pointTo, duration);
arrowAnimator.start();
}
private ValueAnimator createArrowAnimator(Point pointFrom, Point pointTo, int duration) {
final double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);
mPointFrom.x = pointFrom.x;
mPointFrom.y = pointFrom.y;
int firstX = (int) (pointFrom.x + ARROW_SIZE * Math.cos(angle));
int firstY = (int) (pointFrom.y + ARROW_SIZE * Math.sin(angle));
PropertyValuesHolder propertyX = PropertyValuesHolder.ofInt(PROPERTY_X, firstX, pointTo.x);
PropertyValuesHolder propertyY = PropertyValuesHolder.ofInt(PROPERTY_Y, firstY, pointTo.y);
ValueAnimator animator = new ValueAnimator();
animator.setValues(propertyX, propertyY);
animator.setDuration(duration);
// set other interpolator (if needed) here:
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mPointTo.x = (int) valueAnimator.getAnimatedValue(PROPERTY_X);
mPointTo.y = (int) valueAnimator.getAnimatedValue(PROPERTY_Y);
invalidate();
}
});
return animator;
}
}
with .xml layout like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<{YOUR_PACKAGE_NAME}.ArrowLayout
android:id="#+id/arrow_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/first_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="First Button"/>
<Button
android:id="#+id/second_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="Second Button"/>
</{YOUR_PACKAGE_NAME}.ArrowLayout>
</RelativeLayout>
and MainActivity.java like:
public class MainActivity extends AppCompatActivity {
private ArrowLayout mArrowLayout;
private Button mFirstButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mArrowLayout = (ArrowLayout) findViewById(R.id.arrow_layout);
mFirstButton = (Button) findViewById(R.id.first_button);
mFirstButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mArrowLayout.animateArrows(1000);
}
});
}
}
you got something like that (on First Button click):
For other cases ( Second Button is exactly to the left (or above, or below) or more complex above-right/below-left etc. of First Button) you should modify part for calculating arrow begin and end points:
private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {
Point pointFrom = new Point();
// Second Button above
// ----------+----------
// | |
// Second Button tho the left + First Button + Second Button tho the right
// | |
// ----------+----------
// Second Button below
//
// + - is arrow start point position
if (toViewBounds to the right of fromViewBounds){
pointFrom.x = fromViewBounds.right;
pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;
} else if (toViewBounds to the left of fromViewBounds) {
pointFrom.x = fromViewBounds.left;
pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;
} else if () {
...
}
return pointFrom;
}
Use Path and Pathmeasure for Drawing Animated Line. I have Made and test it.
Make Custom View and pass view coordinates points array to it,
public class AnimatedLine extends View {
private final Paint mPaint;
public Canvas mCanvas;
AnimationListener animationListener;
Path path;
private static long animSpeedInMs = 2000;
private static final long animMsBetweenStrokes = 100;
private long animLastUpdate;
private boolean animRunning = true;
private int animCurrentCountour;
private float animCurrentPos;
private Path animPath;
private PathMeasure animPathMeasure;
float pathLength;
float distance = 0;
float[] pos;
float[] tan;
Matrix matrix;
Bitmap bm;
public AnimatedLine(Context context) {
this(context, null);
mCanvas = new Canvas();
}
public AnimatedLine(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(15);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setColor(context.getResources().getColor(R.color.materialcolorpicker__red));
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
}
bm = BitmapFactory.decodeResource(getResources(), R.drawable.hand1);
bm = Bitmap.createScaledBitmap(bm, 20,20, false);
distance = 0;
pos = new float[2];
tan = new float[2];
matrix = new Matrix();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mCanvas = canvas;
if (path != null) {
if (animRunning) {
drawAnimation(mCanvas);
} else {
drawStatic(mCanvas);
}
}
}
/**
* draw Path With Animation
*
* #param time in milliseconds
*/
public void drawWithAnimation(ArrayList<PointF> points, long time,AnimationListener animationListener) {
animRunning = true;
animPathMeasure = null;
animSpeedInMs = time;
setPath(points);
setAnimationListener(animationListener);
invalidate();
}
public void setPath(ArrayList<PointF> points) {
if (points.size() < 2) {
throw new IllegalStateException("Pass atleast two points.");
}
path = new Path();
path.moveTo(points.get(0).x, points.get(0).y);
path.lineTo(points.get(1).x, points.get(1).y);
}
private void drawAnimation(Canvas canvas) {
if (animPathMeasure == null) {
// Start of animation. Set it up.
animationListener.onAnimationStarted();
animPathMeasure = new PathMeasure(path, false);
animPathMeasure.nextContour();
animPath = new Path();
animLastUpdate = System.currentTimeMillis();
animCurrentCountour = 0;
animCurrentPos = 0.0f;
pathLength = animPathMeasure.getLength();
} else {
// Get time since last frame
long now = System.currentTimeMillis();
long timeSinceLast = now - animLastUpdate;
if (animCurrentPos == 0.0f) {
timeSinceLast -= animMsBetweenStrokes;
}
if (timeSinceLast > 0) {
// Get next segment of path
float newPos = (float) (timeSinceLast) / (animSpeedInMs / pathLength) + animCurrentPos;
boolean moveTo = (animCurrentPos == 0.0f);
animPathMeasure.getSegment(animCurrentPos, newPos, animPath, moveTo);
animCurrentPos = newPos;
animLastUpdate = now;
//start draw bitmap along path
animPathMeasure.getPosTan(newPos, pos, tan);
matrix.reset();
matrix.postTranslate(pos[0], pos[1]);
canvas.drawBitmap(bm, matrix, null);
//end drawing bitmap
//take current position
animationListener.onAnimationUpdate(pos);
// If this stroke is done, move on to next
if (newPos > pathLength) {
animCurrentPos = 0.0f;
animCurrentCountour++;
boolean more = animPathMeasure.nextContour();
// Check if finished
if (!more) {
animationListener.onAnimationEnd();
animRunning = false;
}
}
}
// Draw path
canvas.drawPath(animPath, mPaint);
}
invalidate();
}
private void drawStatic(Canvas canvas) {
canvas.drawPath(path, mPaint);
canvas.drawBitmap(bm, matrix, null);
}
public void setAnimationListener(AnimationListener animationListener) {
this.animationListener = animationListener;
}
public interface AnimationListener {
void onAnimationStarted();
void onAnimationEnd();
void onAnimationUpdate(float[] pos);
}
}

Animation to increase the size of a rectangle in custom view

I'm creating a custom view in which I have a rectangle RectF object that have a specific height. I would like to increase the bottom Y point coordinate to a specific value with a progressive animation.
I've tried the following. I've created a method setBatteryState() that is called on a onclicked method in the activity that holds the custom view:
public class BatteryView extends View {
public int mCanvasWidth;
public int mCanvasHeight;
public RectF mBatteryHead;
public RectF mBatteryBody;
public RectF mBatteryBodyVolume;
public Canvas mCanvas;
public BatteryView(Context context) {
super(context);
}
public BatteryView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void init()
{
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
this.mCanvas = canvas;
float batteryHeadDistanceFromLeft = mCanvasWidth / 3;
float batteryHeadWidth = mCanvasWidth / 3;
float batteryBodyDistanceFromTop = mCanvasHeight / 5;
float batteryHeadHeight = mCanvasHeight / 5;
mBatteryHead = new RectF(batteryHeadDistanceFromLeft,0,2*batteryHeadWidth,batteryHeadHeight+5);
Paint batteryHeadPaint = new Paint();
batteryHeadPaint.setColor(ContextCompat.getColor(getContext(), R.color.batifyColor));
canvas.drawRect(mBatteryHead,batteryHeadPaint);
mBatteryBody = new RectF(0,(int)batteryBodyDistanceFromTop,mCanvasWidth,mCanvasHeight);
Paint batteryBodyPaint = new Paint();
batteryBodyPaint.setStyle(Paint.Style.STROKE);
batteryBodyPaint.setColor(ContextCompat.getColor(getContext(), R.color.batifyColor));
batteryBodyPaint.setStrokeWidth(10);
canvas.drawRect(mBatteryBody,batteryBodyPaint);
mBatteryBodyVolume = new RectF(12,(int)batteryBodyDistanceFromTop + 10,mCanvasWidth-12,mCanvasHeight/2);
Paint volumeBodyPaint = new Paint();
volumeBodyPaint.setColor(ContextCompat.getColor(getContext(), R.color.batifyColor));
canvas.drawRect(mBatteryBodyVolume,volumeBodyPaint);
}
public void setStateOnBattery(){
ObjectAnimator animateBottom = ObjectAnimator.ofFloat(mBatteryBodyVolume, "bottom", mBatteryBodyVolume.bottom, mCanvasHeight);
animateBottom.setDuration(1000).start();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
mCanvasWidth = MeasureSpec.getSize(widthMeasureSpec);
mCanvasHeight = MeasureSpec.getSize(heightMeasureSpec);
}}
ObjectAnimator should translate the rect mBatteryBodyVolume to the size of the canvas but nothing change...
Any Idea ?
Thanks in advance !
Use an asynchronous task with 2 major functions, draw and update. Every time update is called, increase the height variable by a constant. Then, in draw, draw your rectangle with height as a param. If you need code, just ask. :D
UPDATE
Create a 'runner' async task:
public class Runner extends Thread {
public volatile boolean running = true;
private Environment env;
public Runner(Environment E) {
env = E;
}
#Override
public void run() {
long lastTime = System.currentTimeMillis();
while(running) {
long now = System.currentTimeMillis();
long elapsed = now - lastTime;
env.update(elapsed);
env.draw();
lastTime = now;
}
}
public void shutdown() {
running = false;
}
}
In Environment, Do the following:
public void draw() {
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawRect(x-w, y-h, x+w, y+h, myPaint);
holder.unlockCanvasAndPost(canvas);
}
}
and the update method:
public void update(float elapsedTime) {
h+=myKonstant*elpasedTime;
}
Hope I Helped :D

Animate draw to canvas in drawable

I have a drawable that draws a progress bar using onLevelChange and calls draw() to make a line at bottom on canvas. How can I animate this line draw?
public class BorderProgressDrawable extends Drawable {
private static final int MAX_LEVEL = 10000;
private int mLevel = 0;
private Paint mPaint;
public BorderProgressDrawable(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(context.getResources().getColor(R.color.accent));
mPaint.setStrokeWidth((float) CommonMethods.dpToPx(context.getResources().getDisplayMetrics(), 4));
}
#Override
public void draw(final Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
float visibleWidth = (((float) mLevel) / MAX_LEVEL) * width;
canvas.drawLine(0, height, visibleWidth, height, mPaint);
}
#Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
if (colorFilter != null) mPaint.setColorFilter(colorFilter);
}
#Override
public int getOpacity() {
return mPaint.getAlpha();
}
#Override
protected boolean onLevelChange(int level) {
mLevel = level;
return true;
}
}

Canvas not clear on android 4.2.2

I have a customview , inside it , i have one view slide up and down , when slide i draw fade background like this :
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawARGB(alpha, 0, 0, 0);
super.dispatchDraw(canvas);
}
It work with my device android 4.2.2 but with android 4.4.2 or google nexus one android 2.3.7 ,it so bad.
2 images below show in my device 4.2.2(slide in and slide out):
http://imgur.com/p6i9gw8
http://imgur.com/9Sdzy7v
and 2 images below show in google nexus one android 2.3.7(slide in and slide out):
http://imgur.com/ZGKiRJi
http://imgur.com/Uf3vRdb
As you can see, in 2 image first, fade draw correct , and in other, it look bad.
Complete code for this view is :
public class SlideView extends ViewGroup {
private static final int MAXDURATIONSLIDE = 500;
protected View content;
private int childHeight;
private int childOffset;
private int childWidth;
private int alpha;
private Fillinger fillinger;
public ISlide getSlideChangeListener() {
return slideListener;
}
public void setSlideChangeListener(ISlide slideChangeListener) {
this.slideListener = slideChangeListener;
}
private ISlide slideListener;
public SlideView(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
if(attrs!=null){
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideView);
try {
int contentLayoutId = a.getResourceId(R.styleable.SlideView_slideView, 0);
DebugLog.d(contentLayoutId);
setContent(contentLayoutId);
} finally {
a.recycle();
}
}
fillinger = new Fillinger(context);
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
DebugLog.d("width "+width+" height "+height);
childOffset = height;
content.layout(0, childOffset, childWidth, childOffset + childHeight);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
measureChild(content, widthMeasureSpec, height);
childHeight = content.getMeasuredHeight();
childWidth = content.getMeasuredWidth();
DebugLog.d("childWidth "+childWidth+" childHeight "+childHeight);
setMeasuredDimension(width, height);
}
public void setContent(int resId) {
View view = LayoutInflater.from(getContext()).inflate(resId, this,false);
setContent(view);
}
public void setContent(View v) {
if (content != null)
removeView(content);
content = v;
addView(content);
}
private void moveViewByY(int diffY) {
childOffset += diffY;
alpha = (int) (Math.abs((getHeight()-childOffset)*255/(childHeight))*0.5f);
content.layout(0, childOffset, childWidth, childOffset + childHeight);
if(slideListener!=null){
slideListener.onSlide(childOffset,childHeight);
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawARGB(alpha, 0, 0, 0);
super.dispatchDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(isIn()&&touchOutSide(event)){
toogle();
return true;
}
return false;
}
private boolean touchOutSide(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
if(x<content.getLeft()||x>content.getRight()||y<content.getTop()||y>content.getBottom()){
return true;
}
return false;
}
public void hide(){
if(isIn()){
fillinger.startScroll(content.getTop(),getHeight(),childHeight,MAXDURATIONSLIDE);
}
}
public void show(){
if(!isIn()){
fillinger.startScroll(content.getTop(),getHeight()-childHeight,childHeight,MAXDURATIONSLIDE);
}
}
public void toogle(){
fillinger.cancleAnimation();
if(isIn()){
hide();
}else{
show();
}
}
public boolean isIn(){
return content.getTop()<getHeight();
}
public class Fillinger implements Runnable {
private static final String tag = "Fillinger";
private Scroller mScroller;
private int lastY;
private boolean more;
private int currentY;
private int diffY;
public Fillinger(Context context) {
mScroller = new Scroller(context);
}
public void startScroll(float startY, float endY, float maxDistance, int maxDurationForFling) {
int duration = (int) Math.min(Math.abs((endY - startY)) / maxDistance * maxDurationForFling, maxDurationForFling);
lastY = (int) startY;
if(slideListener!=null){
slideListener.onStartSlide();
}
mScroller.startScroll(0, (int) startY, 0, -(int) (endY - startY), duration);
setDrawingCacheEnabled(true);
post(this);
}
public void cancleAnimation(){
removeCallbacks(this);
}
#Override
public void run() {
more = mScroller.computeScrollOffset();
currentY = mScroller.getCurrY();
diffY = lastY - currentY;
moveViewByY(diffY);
lastY = currentY;
if (more) {
post(this);
}else{
setDrawingCacheEnabled(false);
if(slideListener!=null){
slideListener.onSlideFinish(isIn());
}
}
}
}
}
What i missing?
Sorry for my bad english.
Thanks all.
Edit: finally , i must call invalidate in moveViewByY method like this
private void moveViewByY(int diffY) {
childOffset += diffY;
alpha = (int) (Math.abs((getHeight()-childOffset)*255/(childHeight))*0.5f);
content.layout(0, childOffset, childWidth, childOffset + childHeight);
invalidate();
if(slideListener!=null){
slideListener.onSlide(childOffset,childHeight);
}
}
I dont know why , may be invalidate , view tree redraw and old canvas cleared.
Expect best solution, thanks .

Hide buttons on start of Android app

I have a MenuView ViewGroup which capsules all the buttons of my interface.
On start of the app all buttons are positioned via button.layout(). Then I hide all buttons, with one exception: the menu button. When I am pressing the menu button, all other buttons are shown (via a zoom animation).
Now the problem: while positioning the buttons via button.layout(), they are visible. So almost 1 sec all buttons are shown on startup of the app.
How can I avoid that?
My button code:
public class CircleButton extends Button {
static final int StateDefault = 0;
static final int StateFocused = 1;
static final int StatePressed = 2;
private Bitmap mBitmapDefault;
private String mCaption;
protected int radius;
protected int color = 0xff000000;
private Typeface font1;
private boolean visible = true;
private int savedLeft, savedTop;
// indicates whether the button is visible when the menu is rendered
public boolean visibleInMenu = true;
private Paint paint;
private static String TAG = "CircleButton";
public int getRadius(){
return radius;
}
public CircleButton setRadius(int radius){
this.radius = radius;
this.setWidth(2*radius);
this.setHeight(2*radius);
return this;
}
public CircleButton setColor(int color){
this.color = color;
return this;
}
public CircleButton setCaption(String caption){
mCaption = caption;
return this;
}
public CircleButton(Context context) {
super(context);
init(context);
}
public CircleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CircleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
setClickable(true);
setBackgroundColor(0x00000000);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
font1 = Typeface.createFromAsset(context.getAssets(), "fonts/Bebas/BEBAS___.ttf");
mCaption = "Caption";
this.setRadius(45);
this.setTextSize(18);
setOnClickListener(onClickListener);
setOnTouchListener(onTouchListener);
}
public void setVisibility(int visibility) {
if(visibility == GONE){
this.visible = false;
}else{
this.visible = true;
}
super.setVisibility(visibility);
}
/**
* draws the normal button (without rollover)
* #param canvas
*/
protected void drawDefault(Canvas canvas){
//canvas.drawARGB(255, 255, 0, 0);
Paint paintText = new Paint();
paintText.setAntiAlias(true);
paintText.setTextSize(this.getTextSize());
paintText.setColor(0xffffffff); // white
paintText.setTypeface(this.font1);
Rect bounds = new Rect();
//Paint textPaint = this.getPaint();
paintText.getTextBounds(mCaption,0,mCaption.length(),bounds);
float left = (float) (2*radius-bounds.width())/2;
float top = (float) radius+bounds.height()/2;
//Log.v("Button","bounds:"+bounds.width()+"x"+bounds.height());
//Log.v("Button","this.getWIdth:"+2*radius);
// create the Drawing Tool (Brush)
Paint paint = new Paint();
paint.setAntiAlias(true); // for a nicer paint
paint.setColor(color);
paint.setStrokeWidth(1);
paint.setStyle(Style.FILL);
Path path = new Path();
path.addCircle(radius, radius, radius, Path.Direction.CW);
canvas.drawPath(path, paint);
canvas.save();
canvas.rotate(-45,this.getRadius(),this.getRadius());
canvas.drawText(mCaption, left, top, paintText);
canvas.restore();
}
protected Bitmap getDefaultBitmap(){
if(mBitmapDefault == null){
mBitmapDefault = Bitmap.createBitmap(2*radius, 2*radius, Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmapDefault);
this.drawDefault(canvas);
return mBitmapDefault;
}
return mBitmapDefault;
}
#Override
protected void onDraw(Canvas canvas) {
//Log.v("Button","onDraw(): "+this.getWidth()+"x"+this.getHeight());
super.onDraw(canvas);
canvas.drawBitmap(this.getDefaultBitmap(), 0, 0, paint);
//super.onDraw(canvas);
//canvas.drawBitmap(this.getDefaultBitmap(), new Rect(0, 0, this.radius*8, this.radius*8), new Rect(0,0, this.radius*2, this.radius*2), null);
}
public void recycle() {
if(mBitmapDefault != null){
mBitmapDefault.recycle();
mBitmapDefault = null;
}
}
public void hide(){
this.hide(true);
}
public void hide(boolean withAnimation){
if(this.visible == true){
savedLeft = getLeft();
savedTop = getTop();
ScaleAnimation anim = new ScaleAnimation(1, 0, 1, 0, this.getRadius(), this.getRadius());
anim.setDuration(300);
anim.setFillAfter(true);
anim.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
visible = false;
layout(0, 0, 0, 0);
}
});
this.startAnimation(anim);
}
}
public void show(){
if(this.visible == false){
this.setVisibility(VISIBLE);
OvershootInterpolator inter = new OvershootInterpolator();
ScaleAnimation anim = new ScaleAnimation(0, 1, 0, 1, this.getRadius(), this.getRadius());
anim.setDuration(300);
anim.setInterpolator(inter);
anim.setFillAfter(true);
anim.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) { }
#Override
public void onAnimationRepeat(Animation animation) { }
#Override
public void onAnimationEnd(Animation animation) {
visible = true;
}
});
this.startAnimation(anim);
this.layout(this.savedLeft, this.savedTop, this.savedLeft+2*this.radius, this.savedTop+2*this.radius);
}
this.visible = true;
}
public CircleButton setOnClickCallback(OnClickListener l) {
super.setOnClickListener(l);
return this;
}
private OnClickListener onClickListener = new OnClickListener() {
#Override
public void onClick(View arg0) {
}
};
private OnTouchListener onTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
//Log.v("Button","onTouch()");
if(event.getAction() == MotionEvent.ACTION_DOWN){
OvershootInterpolator inter = new OvershootInterpolator();
ScaleAnimation anim = new ScaleAnimation(1, (float) 1.5, 1,(float) 1.5, CircleButton.this.getRadius(), CircleButton.this.getRadius());
anim.setInterpolator(inter);
anim.setDuration(200);
anim.setFillAfter(true);
CircleButton.this.startAnimation(anim);
}else if(event.getAction() == MotionEvent.ACTION_UP){
ScaleAnimation anim = new ScaleAnimation((float) 1.5, 1, (float) 1.5, 1, CircleButton.this.getRadius(), CircleButton.this.getRadius());
anim.setDuration(300);
anim.setFillAfter(true);
CircleButton.this.startAnimation(anim);
}
return false;
}
};
}
Use setVisibility(int visibility) method on the object to hide it with Parameter visibility One of VISIBLE, INVISIBLE, or GONE:
GONE
This view is invisible, and it doesn't take any space for layout purposes. Use with setVisibility(int) and android:visibility.
Constant Value: 8 (0x00000008)
INVISIBLE
This view is invisible, but it still takes up space for layout purposes. Use with setVisibility(int) and android:visibility.
Constant Value: 4 (0x00000004)
VISIBLE
This view is visible. Use with setVisibility(int) and android:visibility.
Constant Value: 0 (0x00000000)
Try putting android:visibility="invisible" to those buttons in your layout xml.

Categories

Resources