How to slow down an animation in its middle while being executed? - android

As the question is clearly stated, I want to slow down a TranslateAnimation while it is being executed when a user clicks on a certain button.
This is how I instantiate the TranslateAnim where -textViewHeight and layoutHeight are just some values I already instantiated in earlier time:
It goes from up to bottom.
TranslateAnim translateAnim = new TranslateAnim(0, 0, -textViewHeight, layoutHeight);
translateAnim.setDuration(20000);
translateAnim.setInterpolator(new LinearInterpolator());
textView.startAnimation(translateAnim);
This is the method where I want to slow the TranslateAnim down:
It didn't work as I expected though.
public void slowDown() {
translateAnim.setStartTime(translateAnim.getStartTime() - 20000);
translateAnim.setDuration((20000 - translateAnim.getElapsedTime()) * 2);
}
I also tried doing that but still no luck:
translateAnim.setInterpolator(new DecelerateInterpolator());
My custom TranslateAnim class extending TranslateAnimation:
public class TranslateAnim extends TranslateAnimation {
private long mElapsedAtPause, elapsedTime;
private boolean mPaused = false;
public TranslateAnim(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
super(fromXDelta, toXDelta, fromYDelta, toYDelta);
}
#Override
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mPaused && mElapsedAtPause == 0) {
mElapsedAtPause = currentTime - getStartTime();
}
if (mPaused) {
setStartTime(currentTime - mElapsedAtPause);
}
elapsedTime = currentTime - getStartTime();
return super.getTransformation(currentTime, outTransformation);
}
public long getElapsedTime() {
return elapsedTime;
}
public void slowDown() {
translateAnim.setStartTime(translateAnim.getStartTime() - 20000);
translateAnim.setDuration((20000 - translateAnim.getElapsedTime()) * 2);
}
public void pause() {
mElapsedAtPause = 0;
mPaused = true;
}
public void resume() {
mPaused = false;
}
#Override
public void cancel() {
super.cancel();
elapsedTime = 0;
mElapsedAtPause = 0;
mPaused = false;
}
}
Is there any workaround for this?

Use a custom Interpolator instead of a linear one. A linear one causes it to have equal time slices. A custom one can have the time slices in the middle be longer.
For example:
public class CustomInterpolator implements Interpolator {
public boolean slowMode;
float lastInput;
float lastInputBeforeSlowed;
#Override
public float getInterpolation(float input) {
if (!slowMode) {
//Should be edited
lastInput = input;
return input;
} else {
return (input - lastInputBeforeSlowed) * .5f + lastInputBeforeSlowed;
}
}
public void enterSlowMode() {
slowMode = true;
lastInputBeforeSlowed = lastInput;
}
public void endSlowMode() {
slowMode = false;
//Should be edited
}
}

Related

FloatingActionButton does not return to original size after animation

I wrote a little STT-functionality, with a floating button that is pulsating after being clicked on to notify that the app is listening. This works quite well so far with the one annoying behavior that my floating button does not return to its original size in some cases.
The animation increases and decreases the size of the button, and I guess it gets stuck in the increased state, hence the randomness of this behavior. I just can't figure out how to catch that and set the size to the original one.
Action Listener of my Button:
private View.OnTouchListener setVoiceButtonOnClick()
{
return new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!voiceButton.isInitialized())
voiceButton.initAnimationValues();
voiceButton.setPressed(true);
listen();
}
return true;
}
};
}
My Button extends FloatingActionButton, and does the following:
public class FloatingVoiceButton extends FloatingActionButton
{
public static final float DEFAULT_ANIMATION_FACTOR = 1.2f;
private boolean isInitialized = false;
private int originalHeight;
private int originalWidth;
private boolean isAnimationRunning;
private ObjectAnimator animator;
public FloatingVoiceButton(Context context)
{
super(context);
}
public void initAnimationValues()
{
isInitialized = true;
isAnimationRunning = false;
originalHeight = getMeasuredHeight();
originalWidth = getMeasuredWidth();
animator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", DEFAULT_ANIMATION_FACTOR),
PropertyValuesHolder.ofFloat("scaleY", DEFAULT_ANIMATION_FACTOR));
animator.setDuration(200);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setRepeatMode(ObjectAnimator.REVERSE);
}
public boolean isInitialized()
{
return isInitialized;
}
public void resetButtonSize()
{
setMeasuredDimension(originalWidth, originalHeight);
}
public boolean isAnimationRunning()
{
return isAnimationRunning;
}
public void animate(boolean doAnimation)
{
isAnimationRunning = doAnimation;
if(doAnimation)
animator.start();
else
{
animator.end();
setPressed(false);
resetButtonSize();
//destroyDrawingCache(); tried these without success
//postInvalidate();
}
}
}
Finally I am controlling the button the start and end of the animation with my RecognitionListener:
public class InputVoiceRecognitionListener implements RecognitionListener
{
private EditText targetEditText;
private String originalContent;
private final String DELIMITER = "\n\n";
private FloatingVoiceButton button;
public InputVoiceRecognitionListener(EditText editText, FloatingVoiceButton button)
{
targetEditText = editText;
originalContent = editText.getText().toString();
this.button = button;
}
#Override
public void onReadyForSpeech(Bundle params)
{
button.animate(true);
}
#Override
public void onBeginningOfSpeech()
{
originalContent = targetEditText.getText().toString();
}
#Override
public void onRmsChanged(float rmsdB)
{}
#Override
public void onBufferReceived(byte[] buffer)
{}
#Override
public void onEndOfSpeech()
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onError(int error)
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onResults(Bundle results)
{
setRecognizedText(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onPartialResults(Bundle partialResults)
{
setRecognizedText(partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onEvent(int eventType, Bundle params)
{
}
private void setRecognizedText(ArrayList<String> matches)
{
String result = "";
if(matches != null)
result = matches.get(0);
if((originalContent.trim()).length() > 0)
{
if(!originalContent.endsWith("\n\n"))
result = originalContent + DELIMITER + result;
else result = originalContent + result;
}
targetEditText.setText(result);
targetEditText.setSelection(result.length());
}
}
EDIT
This did it for me:
resettingAnimator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f));
resettingAnimator.setDuration(0);
resettingAnimator.setRepeatCount(1);
and calling resettingAnimator.start(); when I finish my main animation.
Simple solution to this problem is that you define another animation after stopping your repeating one.
I just can't figure out how to catch that and set the size to the original one.
You, that is View, does know what is the "original" size, its the size of the scale factor 1f. So after stopping repeating animation just make another animations to set scale to 1f
PropertyValuesHolder.ofFloat("scaleX", 1f)
PropertyValuesHolder.ofFloat("scaleY", 1f))
This animation will run always, but will not be visible if your button is already at "normal" size.
With this in mind I would recommend that you use some other flag than isAnimationRunning(), either by some state (ex. selected) of your Fab, or some manually set arbitrary boolean.

valueanimator.start is restaring the animation after valueanimator.cancel on API < 19

i know this is asked before i tried all the solution but i cannot arrive to fix it,
i have objects that fall from the top of the screen to the bottom of the screen using translation in android library.
i want to the pause the animation and resume it:
PAUSE CODE fixed from the suggestion on one of the stack overflow posts:
private void pauseAnimation() {
p = true;
Pausearray = new MyClass[mAllImageViews.size()];
int count = 0;
for (ArrowView v : mAllImageViews) {
ValueAnimator va = (ValueAnimator) v.getTag();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
va.pause();
} else {
if (va != null) {
MyClass temp = new MyClass();
temp.tag = v.getTag().toString();
temp.playtime = va.getCurrentPlayTime();
Pausearray[count] = temp;
va.cancel();
}
}
count ++;
}
}
the pause is working the annimation is stoping from moving and its staying in its position.
RESUME CODE suggested by one of the stack overflow posts:
private void resumeAnimation() {
p = false;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
for (ArrowView v : mAllImageViews) {
try {
ValueAnimator va = (ValueAnimator) v.getTag();
va.resume();
} catch (Exception ex) {
}
}
} else {
if(Pausearray!=null) {
for (ArrowView v : mAllImageViews) {
ValueAnimator va = (ValueAnimator) v.getTag();
for (int i = 0; i < Pausearray.length; i++) {
if(v.getTag().toString().equalsIgnoreCase(Pausearray[i].tag)){
va.start();
va.setCurrentPlayTime(Pausearray[i].playtime);
}
}
}
}
}
}
the problem with the resume that it's starting from the top again i want it to continue where it was it's position on screen. Thank you!
Update:
i tried to get the arrow view position from screen and set it at resume, i'm still getting the same issue, views are restarting so the edited code is:
private void resumeAnimation() {
p = false;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
for (ArrowView v : mAllImageViews) {
try {
ValueAnimator va = (ValueAnimator) v.getTag();
va.resume();
} catch (Exception ex) {
}
}
} else {
if(Pausearray!=null) {
for (ArrowView v : mAllImageViews) {
ValueAnimator va = (ValueAnimator) v.getTag();
for (int i = 0; i < Pausearray.length; i++) {
if(v.getTag().toString().equalsIgnoreCase(Pausearray[i].tag)){
Log.w("PAUSETEST","Setting Parameters "+Pausearray[i].playtime);
va.start();
va.setCurrentPlayTime(Pausearray[i].playtime);
v.setY(Pausearray[i].translationy); // i took the location via v.gettranslationY on pause
}
}
}
}
}
}
UPDATE:
i heard while i was searching that valueanimator doesn't restart so i switched the valueanimator to object animator animator code:
public void startAnimation(final ArrowView aniView) {
aniView.setPivotX(aniView.getWidth());
aniView.setPivotY(aniView.getHeight());
long delay = new Random().nextInt(Constants.MAX_DELAY);// generate a random delay time before the animation starts to go down
ObjectAnimator animator = ObjectAnimator.ofFloat(aniView,"translationY", mDisplaySize.bottom - (buttonsize) - (90 * mScale));
if (aniView.getColor() == 0 && aniView.draw == false) {
Constants.ANIM_DURATION = Constants.ANIM_DURATION_NORMAL;
} else if (aniView.getColor() != 0 && aniView.draw == false) {
Constants.ANIM_DURATION = Constants.ANIM_DURATION_COLOR;
} else if (aniView.getColor() == 0 && aniView.draw == true) {
Constants.ANIM_DURATION = Constants.ANIM_DURATION_COLOR;
} else if (aniView.getColor() != 0 && aniView.draw == true) {
Constants.ANIM_DURATION = Constants.ANIM_DURATION_COLORANDCALC;
}
animator.setDuration(Constants.ANIM_DURATION);// speed of the falldown
animator.setInterpolator(new AccelerateInterpolator());
animator.setStartDelay(delay);// start the animation after the delay.
// animator.addUpdateListener(new AnimatorUpdateListener() {
//
// //int angle = 50 + (int) (Math.random() * 101);
// //int movex = new Random().nextInt(mDisplaySize.right);
//
// #Override
// public void onAnimationUpdate(ValueAnimator animation) {
// float value = ((Float) (animation.getAnimatedValue())).floatValue();
// //aniView.setRotation(angle*value);
// //aniView.setTranslationX((movex-40)*value);
// aniView.setTranslationY((mDisplaySize.bottom - (buttonsize)) * value - (90 * mScale));// set the translation to fall down and stop on the top of buttons
// }
// });
aniView.setTag(animator);
animator.start();
}
and i changed the code of the pause/resume to cast it into object animator and I'm still facing the same problem... on start is restarting the animation! please someone help! thanks!
After Playing around with valueanimator class i figured out a trick to make the animation stop, and resume its not an authentic way, but it solves the problem, i extended from value animator class and replaced the normal animator in my main class with the myvalueanimator class and here the play around in the value animator class:
import android.animation.ValueAnimator;
import android.util.Log;
import java.util.Timer;
/**
* Created by win7 on 6/21/2017.
*/
public class MyValueAnimator extends ValueAnimator {
private float animatedValue = 0;
private long currenttime;
private long totaltime;
private boolean onetime = true;
private volatile boolean mPaused = false;
private Timer t;
public void pause() {
animatedValue = (float) getAnimatedValue();
mPaused = true;
}
// #Override
// public void setValues(PropertyValuesHolder... values) {
// if (mPaused)
// return;
// super.setValues(values);
#Override
public Object getAnimatedValue() {
if (mPaused) {
if(onetime){
currenttime = getCurrentPlayTime();
// totaltime = getStartDelay()+ (getDuration() * (getRepeatCount() + 1));
totaltime = getDuration();
setDuration(Long.parseLong("9999999999999999"));
Log.w("PauseDur",Long.parseLong("9999999999999999")+"");
onetime =false;
}
return animatedValue;
}
return super.getAnimatedValue();
}
public void resume() {
mPaused = false;
onetime=true;
setCurrentPlayTime(currenttime);
setDuration(totaltime);
}
public MyValueAnimator(float from, float to) {
setFloatValues(from, to);
}
}

LinearLayout scroll to the top

I write a RecyclerViewRefresh follow SwipeRefreshLayout.class. When I pull the view until it doesn't move and then release,the view reset to the original. The issue is that view should trigger Timer and then that Timer reset the view. I couldn't find the reason.
Please tell me why offsetTopAndBottom() can make the view automatically back to the original place. Thanks.
I use setY() to solve this problem. But I also want to know why. And I read the offsetTopAndBottom()'s source,also can not find any clue.
RecyclerViewRefresh's code:
public class RecyclerViewRefresh extends LinearLayout {
private static final String LOG_TAG=RecyclerViewRefresh.class.getSimpleName();
private static final int INVALID_POINTER=-1;
//Default offset in dips from the top of the view to where the progress
//spinner should stop
private static final int DEFAULT_CIRCLE_TARGET=64;
private static final float DRAG_RATE=.5f;
private View headerView,footerView,thisView;
private View mTarget; //the target of the gesture
private ImageView arrowIv;
private TextView refreshTv;
private ProgressBar progressBar;
private OnPullToRefresh refreshListener=null;
private OnDragToLoad loadListener=null;
float startY=0;
private int headerHeight=0;
private boolean mReturningToStart;
private boolean mRefreshing=false;
private boolean mNestedScrollInProgress;
private int mCurrentTargetOffsetTop;
protected int mOriginalOffsetTop;
private boolean mIsBeingDragged;
private int mActivePointerId=INVALID_POINTER;
private float mInitailDownY;
private int mTouchSlop;
private float mTotalDragDistance=-1;
private float mInitialMotionY;
private float mSpinnerFinalOffset;
private boolean updateHeader=true;
private Handler handler=new Handler();
private Timer timer;
public RecyclerViewRefresh(Context context) {
super(context);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context)
{
thisView=this;
mTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
headerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null);
footerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null);
measureView(headerView);
arrowIv=(ImageView)headerView.findViewById(R.id.arrow);
refreshTv=(TextView)headerView.findViewById(R.id.tip);
progressBar=(ProgressBar)headerView.findViewById(R.id.progress);
headerHeight=headerView.getMeasuredHeight();
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
headerView.getMeasuredHeight());
this.addView(headerView,lp);
setTopHeader(headerHeight);
final DisplayMetrics metrics=getResources().getDisplayMetrics();
mSpinnerFinalOffset=DEFAULT_CIRCLE_TARGET*metrics.density;
mTotalDragDistance=mSpinnerFinalOffset;
}
/**
* 通知父布局,占用的宽,高;
*
* #param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int height;
int tempHeight = p.height;
if (tempHeight > 0) {
height = MeasureSpec.makeMeasureSpec(tempHeight,
MeasureSpec.EXACTLY);
} else {
height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
private void setTopHeader(int height)
{
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
{
this.setY(-height);
}else{
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
lp.topMargin=-height;
this.setLayoutParams(lp);
}
headerView.invalidate();
}
/**
* Set the listener to be notified when a refresh is triggered via the
* pull gesture.
* #param listener
*/
public void setOnPullToRefresh(OnPullToRefresh listener)
{
this.refreshListener=listener;
}
/**
* Set the listener to be notified when a load is triggered via the
* drag gesture
* #param listener
*/
public void setOnDragToLoad(OnDragToLoad listener)
{
this.loadListener=listener;
}
private void ensureTarget(){
if(mTarget==null){
for(int i=0;i<getChildCount();i++)
{
View child=getChildAt(i);
if(child instanceof RecyclerView)
{
mTarget=child;
break;
}
}
}
}
/**
* #return Whether it is possible for the child view of this layout to
* scroll up.Override this if the child view is a custom view.
*/
public boolean canChildScrollUp(){
if(mTarget==null)
{
ensureTarget();
}
if(Build.VERSION.SDK_INT<14)
{
if(mTarget instanceof AbsListView)
{
final AbsListView absListView=(AbsListView)mTarget;
return absListView.getChildCount()>0
&&(absListView.getFirstVisiblePosition()>0
||absListView.getChildAt(0).getTop()<absListView.getPaddingTop());
}else{
return ViewCompat.canScrollVertically(mTarget,-1)|| mTarget.getScrollY()>0;
}
}else{
return ViewCompat.canScrollVertically(mTarget,-1);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action=MotionEventCompat.getActionMasked(ev);
if(mReturningToStart && action == MotionEvent.ACTION_DOWN){
mReturningToStart = false;
}
if(!isEnabled() || mReturningToStart || canChildScrollUp()
||mRefreshing || mNestedScrollInProgress){
return false;
}
switch (action){
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop-headerView.getTop(),true);
mActivePointerId=MotionEventCompat.getPointerId(ev,0);
mIsBeingDragged=false;
final float initialDownY=getMotionEventY(ev,mActivePointerId);
if(initialDownY==-1){
return false;
}
mInitailDownY=initialDownY;
updateHeader=true;
break;
case MotionEvent.ACTION_MOVE:
if(mActivePointerId==INVALID_POINTER){
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
}
final float y=getMotionEventY(ev,mActivePointerId);
if(y==-1){
return false;
}
final float yDiff=y-mInitailDownY;
if(yDiff>mTouchSlop && !mIsBeingDragged){
mInitialMotionY=mInitailDownY+mTouchSlop;
mIsBeingDragged=true;
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged=false;
mActivePointerId=INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
private float getMotionEventY(MotionEvent ev,int activePointerId){
final int index=MotionEventCompat.findPointerIndex(ev,activePointerId);
if(index<0){
return -1;
}
return MotionEventCompat.getY(ev,index);
}
private void setTargetOffsetTopAndBottom(int offset,boolean requiresUpdate){
if(this.getTop()<headerHeight+5)
{
this.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop=this.getTop();
if(requiresUpdate && Build.VERSION.SDK_INT<11){
invalidate();
}
if(this.getTop()>headerHeight)
{
if(updateHeader){
updateHeader=false;
refreshTv.setText(getResources().getText(R.string.releasetorefresh));
RotateAnimation animation=new RotateAnimation(0,180,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(800);
animation.setFillAfter(true);
arrowIv.startAnimation(animation);
}
}
}
}
private void onSecondaryPointerUp(MotionEvent ev){
final int pointerIndex=MotionEventCompat.getActionIndex(ev);
final int pointerId=MotionEventCompat.getPointerId(ev,pointerIndex);
if(pointerId==mActivePointerId){
//This was our active pointer going up. Choose a new
//active pointer and adjust accordingly.
final int newPointerIndex=pointerIndex==0?1:0;
mActivePointerId=MotionEventCompat.getPointerId(ev,newPointerIndex);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
final int action=MotionEventCompat.getActionMasked(event);
int pointerIndex=-1;
if(mReturningToStart&&action==MotionEvent.ACTION_DOWN){
mReturningToStart=false;
}
if(!isEnabled() || mReturningToStart
|| canChildScrollUp() || mNestedScrollInProgress){
//Fail fast if we're not in a state where a swipe is possible
return false;
}
switch(action){
case MotionEvent.ACTION_DOWN:
mActivePointerId=MotionEventCompat.getPointerId(event,0);
mIsBeingDragged=false;
break;
case MotionEvent.ACTION_MOVE:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
final float overscrollTop=(y-mInitialMotionY)*DRAG_RATE;
if(mIsBeingDragged){
if(overscrollTop>0){
moveSpinner(overscrollTop);
}else{
return false;
}
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN:{
pointerIndex=MotionEventCompat.getActionIndex(event);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
return false;
}
mActivePointerId=MotionEventCompat.getPointerId(event,pointerIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(event);
break;
case MotionEvent.ACTION_UP:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
mIsBeingDragged=false;
finishSpinner();
mActivePointerId=INVALID_POINTER;
return false;
}
case MotionEvent.ACTION_CANCEL:
return false;
}
return true;
}
private void moveSpinner(float overscrollTop){
float originalDragPercent=overscrollTop/mTotalDragDistance;
float dragPercent=Math.min(1f,Math.abs(originalDragPercent));
float adjustedPercent=(float)Math.max(dragPercent-.4,0)*5/3;
float extraOS=Math.abs(overscrollTop)-mTotalDragDistance;
float slingshotDist=mSpinnerFinalOffset;
float tensionSlingshotPercent=Math.max(0,Math.min(extraOS,slingshotDist*2)/slingshotDist);
float tensionPercent=(float)((tensionSlingshotPercent/4)-Math.pow(
(tensionSlingshotPercent/4),2))*2f;
float extraMove=(slingshotDist)*tensionPercent*2;
int targetY=mOriginalOffsetTop+(int)((slingshotDist*dragPercent)+extraMove);
setTargetOffsetTopAndBottom(targetY-mCurrentTargetOffsetTop,true);
}
private void finishSpinner(){
if(this.getTop()>headerHeight){
setRefreshing(true,true);
}else{
//cancel refresh
mRefreshing=false;
animateOffsetToStartPosition();
}
}
private void setRefreshing(boolean refreshing,final boolean notify)
{
if(mRefreshing!=refreshing){
ensureTarget();
mRefreshing=refreshing;
if(mRefreshing){
refreshListener.onRefresh();
arrowIv.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}else{
arrowIv.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
animateOffsetToStartPosition();
}
}
}
public void setRefreshing(boolean refreshing){
if(!refreshing){
setRefreshing(refreshing,false);
}
}
private void animateOffsetToStartPosition(){
refreshTv.setText(getResources().getText(R.string.pulltorefresh));
arrowIv.clearAnimation();
Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm"));
if(timer==null&&this.getTop()>0)
{
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if(thisView.getTop()>0)
{
thisView.offsetTopAndBottom(-1);
mCurrentTargetOffsetTop = headerView.getTop();
if ( Build.VERSION.SDK_INT < 11) {
invalidate();
}
}else{
Log.d(LOG_TAG,"cancel");
timer.cancel();
timer=null;
}
}
});
}
},10,10);
}
}
/**
* Classes that wish to be notified when the pull gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnPullToRefresh{
public void onRefresh();
}
/**
* Classes that wish to be notified when the drag gesture correctly
* triggers a load should implement this interface.
*/
public interface OnDragToLoad{
public void onLoad();
}}
offsetTopAndBottom(offset) will add mTop and mBottom of View by offset.
private void animateOffsetToStartPosition(){
refreshTv.setText(getResources().getText(R.string.pulltorefresh));
arrowIv.clearAnimation();
Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm"));
if(timer==null&&this.getTop()>0)
{
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if(thisView.getTop()>0)
{
//this line says that if top of thisView is not 0,add mtop and mBottom of thisView by -1
//this timer will change the mTop to 0.thisView will be back to original place if mTop == 0.
thisView.offsetTopAndBottom(-1);
mCurrentTargetOffsetTop = headerView.getTop();
if ( Build.VERSION.SDK_INT < 11) {
invalidate();
}
}else{
Log.d(LOG_TAG,"cancel");
timer.cancel();
timer=null;
}
}
});
}
},10,10);
}
}
setY() calls setTranslationY(), which makes two calls to invalidateViewProperty(boolean invalidateParent, boolean forceRedraw).
In setTranslationY(), when it calls invalidateViewProperty, it is passing forceRedraw as true, which redraws the view and puts it back to the original state.

Starting chronometer with reDraw

Hi I am working on an android application which involves redrawing of the canvas and starting of chronometer at the same time. Anybody knows how can this be achieved?
I have tried to call chronometer.start in View class when invalidate() is called. However, only the canvas is redrawn and the chronometer did not start at all.
EDIT:
Here's the code I tried:
public class ReDraw extends View{
public ReDraw(Context context){
super(context);
this.selfPointer = this;
setFocusable(true);
chrono(context);
}
public void chrono(Context context){
chrono = new Chronometer(context);
chrono.setOnChronometerTickListener(new OnChronometerTickListener(){
public void onChronometerTick(Chronometer arg){
elapsedTime = (SystemClock.elapsedRealtime() - arg.getBase()) / 1000;
long milliseconds= (long) (elapsedTime/60);
String millisec=Long.toString(milliseconds);
arg.setText(millisec);
}
});
chrono.setBase(SystemClock.elapsedRealtime());
chrono.start();
}
}
protected void onDraw(Canvas Square)
{
super.onDraw(Square);
Paint squareColor = new Paint();
squareColor.setColor(Color.BLACK);
Square.drawRect(200,100,200,100, squareColor);
return;
}
}
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
invalidate();
}
return;
}
}
Your invalidate() method will only call onDraw() method not the constructor of the ReDraw.
That's why u only the canvas is redrawn.
try chrono.start() after drawing the square
i.e.
Square.drawRect(200,100,200,100, squareColor);
chrono.start()
UPDATE
try this
public class ReDraw extends View{
String currentTime="00:00:00";
public ReDraw(Context context){
super(context);
this.selfPointer = this;
setFocusable(true);
chrono(context);
}
public void chrono(Context context){
chrono = new Chronometer(context);
chrono.setOnChronometerTickListener(new OnChronometerTickListener(){
public void onChronometerTick(Chronometer arg){
String HH =((elapsedTime / 3600) < 10 ? "0" : "") + (elapsedTime / 3600);
String MM =((elapsedTime / 60) < 10 ? "0" : "") + (elapsedTime / 60);
String SS =((elapsedTime % 60) < 10 ? "0" : "") + (elapsedTime % 60);
currentTime = HH+":"+MM+":"+SS;
elapsedTime = (SystemClock.elapsedRealtime() - arg.getBase()) / 1000;
arg.setText(currentTime);
}
});
chrono.setBase(SystemClock.elapsedRealtime());
chrono.start();
}
}
protected void onDraw(Canvas Square)
{
super.onDraw(Square);
Paint squareColor = new Paint();
squareColor.setColor(Color.BLACK);
Square.drawRect(200,100,200,100, squareColor);
return;
}
}
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
chrono.start();
}
return true;
}
}

ViewGroup invalidation after offsetLeftAndRight

I want one of my ViewGroup to be movable. Let's say, I want to click on it and the content below it reveals. On second click it closes again. Kind of sliding menu behaviour.
Here are my classes:
Custom scroller:
public class MyScroller implements Runnable {
private static final String TAG = "MY_SCROLLER";
private static final int ANIMATION_DURATION = 500;
private final Scroller mScroller;
private View mScrollingView;
private int lastX = 0;
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
MyScroller(final View view) {
mScroller = new android.widget.Scroller(view.getContext(), sInterpolator);
mScrollingView = view;
}
public void start(int initialVelocity) {
Log.d(TAG, "start() called");
int initialX = mScrollingView.getScrollX();
int maxX = mScrollingView.getWidth();
mScroller.startScroll(0, 0, maxX, 0, ANIMATION_DURATION);
lastX = initialX;
mScrollingView.post(this);
}
public void run() {
Log.d(TAG, "run() called");
if (mScroller.isFinished()) {
mScrollingView.invalidate();
return;
}
boolean more = mScroller.computeScrollOffset();
int x = mScroller.getCurrX();
int diff = x - lastX;
if (diff != 0) {
Log.d(TAG, "x = " + x);
mScrollingView.offsetLeftAndRight(diff);
mScrollingView.invalidate();
lastX = x;
} else {
forceFinished();
}
if (more) {
mScrollingView.postDelayed(this, 16);
}
}
void forceFinished() {
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
mScrollingView.invalidate();
}
}
}
and usage:
viewGroup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new MyScroller(v).start(0);
}
});
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new MyScroller(v).start(0);
}
});
For Button everything works fine - it smoothly moves.
But ViewGroups that I tried leave a trail like on screenshot.
RelativeLayout with Button inside left a trail.
Standalone Button moved perfectly.
What am I missing here?

Categories

Resources