How to stop animationset in Android? - android

I have following class. Depends on something I should start/stop animation dynamically. I'm able to run the animation by calling its startAnimation() method however, animation does not stop when I call stopAnimation(). The not interesting thing is even ivSonar1 and ivSonar2 are still visible after calling stopAnimation() method.
Any idea would be appreciated. thanks.
public class SonarView extends RelativeLayout
{
private static final int ANIM_LENGTH_IN_MS = 1500;
private ImageView ivSonar1;
private ImageView ivSonar2;
private AnimationSet animationSet1;
private AnimationSet animationSet2;
public SonarView(Context context)
{
super(context);
}
public SonarView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public SonarView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SonarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// try to make it 25% smaller than its real size to make enough room for items of biding list
super.onMeasure(widthMeasureSpec * 3 / 4, heightMeasureSpec * 3 / 4);
ivSonar1 = (ImageView) findViewById(R.id.ivSonar1);
ivSonar1.setVisibility(INVISIBLE);
ivSonar2 = (ImageView) findViewById(R.id.ivSonar2);
ivSonar2.setVisibility(INVISIBLE);
float maxWidth = findViewById(R.id.ivBackground).getMeasuredWidth();
animationSet1 = createAnimationSet(ivSonar1, maxWidth / ivSonar1.getMeasuredWidth());
animationSet2 = createAnimationSet(ivSonar2, maxWidth / ivSonar2.getMeasuredWidth());
animationSet2.setStartOffset(150);
}
#Override
protected void onFinishInflate()
{
super.onFinishInflate();
LayoutInflater.from(this.getContext()).inflate(R.layout.widget_sonar_view, this, true);
}
private AnimationSet createAnimationSet(final View v, final float toScale)
{
// Set animation
final AnimationSet animationSet = new AnimationSet(true);
animationSet.setInterpolator(new AccelerateDecelerateInterpolator());
animationSet.setDuration(SonarView.ANIM_LENGTH_IN_MS);
animationSet.setAnimationListener(new Animation.AnimationListener()
{
#Override
public void onAnimationStart(Animation animation)
{
v.setAlpha(1.0f);
}
#Override
public void onAnimationEnd(Animation animation)
{
v.setAlpha(0.0f);
animationSet.setStartOffset(0);
v.startAnimation(animationSet);
}
#Override
public void onAnimationRepeat(Animation animation)
{
}
});
// TODO The scale animation should ideally scaled to the size of the outer ring, hacking it here instead to use alpha so that I don't have to the calculation right now. Will come back to this.
// Alpha animation
animationSet.addAnimation(new AlphaAnimation(1.0f, 0.0f));
// Scale animation
animationSet.addAnimation(new ScaleAnimation(0.0f, toScale, 0.0f, toScale, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f));
return animationSet;
}
public void stopAnimation()
{
if (ivSonar1 == null || ivSonar2 == null)
{
return;
}
ivSonar1.setVisibility(INVISIBLE);
ivSonar2.setVisibility(INVISIBLE);
animationSet1.cancel();
animationSet2.cancel();
ivSonar1.clearAnimation();
ivSonar2.clearAnimation();
}
public void startAnimation()
{
if (ivSonar1 == null || ivSonar2 == null)
{
return;
}
ivSonar1.setVisibility(VISIBLE);
ivSonar2.setVisibility(VISIBLE);
animationSet1.reset();
animationSet2.reset();
animationSet2.setStartOffset(150);
ivSonar1.startAnimation(animationSet1);
ivSonar2.startAnimation(animationSet2);
}
}

To stop the AnimationSet you may need to also remove any listeners:
animationSet.setAnimationListener(null);
animationSet.cancel();

Related

Android fragmented ProgressBar (like defrag or bittorrent)

I need an advise on optimizing a custom indicator, that shows progress of downloading file in multiple chunks, in concurrent threads. I couldn't find a correct name for that type - pieces, fragments, chunks? But it should look like bittorrent progress bar or defrag progress from Win XP. And it looks like this:
My custom ProgressBar class as following:
public class FragmentedProgressBar extends ProgressBar {
private int height;
private float fragWidth;
private final ArrayList<Integer> stateColors = new ArrayList<>();
private final Paint progressPaint = new Paint();
private ConcurrentHashMap<Integer, Integer> barData;
public FragmentedProgressBar(Context context) {
super(context);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
this.init(context);
}
public FragmentedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init(context);
}
#SuppressWarnings("deprecation")
private void init(Context context) {
barData = new ConcurrentHashMap<>();
stateColors.addAll(
Arrays.asList(
context.getResources().getColor(android.R.color.holo_blue_dark),
context.getResources().getColor(android.R.color.holo_green_light),
context.getResources().getColor(android.R.color.holo_orange_light),
context.getResources().getColor(android.R.color.holo_red_light)
)
);
}
public synchronized void setProgress(int progress, int state) {
/* state serves to indicate "started", "ready", "retry", "error" by color */
if(barData != null ) {
barData.put(progress, state);
}
super.setProgress(progress);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
height = getMeasuredHeight();
setMeasuredDimension(width, height);
fragWidth = (float) width / getMax();
}
#Override
protected synchronized void onDraw(Canvas canvas) {
for (Map.Entry<Integer, Integer> ent : barData.entrySet()) {
int id = ent.getKey();
int state = ent.getValue();
float xleft = fragWidth * ( id - 1 );
progressPaint.setColor(stateColors.get(state));
canvas.drawRect(xleft, 0.0f, xleft + fragWidth, 0.0f + height, progressPaint);
}
}
}
However, in this approach, it redraws whole bar on every progress tick, and, I think, it's quite inefficient.
I've done formerly same bar in javafx, extending Canvas and drawing each chunk separately on it.
What will be a better solution for android, desirably extending and reusing the ProgressBar class?
Thanks

How to make custom dotted progress bar in android?

Custom progress bar with dots applied animation and given the traversing visual effect. Posting this code here because it can help you to understand and implement new designs too keeping this as reference. Hope this helps you people.
MainActivity.java :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rect"
android:gravity="center"
>
<com.example.horizontal.canvaslearn.HorizontalDottedProgress
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></com.example.horizontal.canvaslearn.HorizontalDottedProgress>
</LinearLayout>
HorizontalDottedProgress.java :
This is a custom class to create dots with animation applied.
public class HorizontalDottedProgress extends View{
//actual dot radius
private int mDotRadius = 5;
//Bounced Dot Radius
private int mBounceDotRadius = 8;
//to get identified in which position dot has to bounce
private int mDotPosition;
//specify how many dots you need in a progressbar
private int mDotAmount = 10;
public HorizontalDottedProgress(Context context) {
super(context);
}
public HorizontalDottedProgress(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalDottedProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//Method to draw your customized dot on the canvas
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
//set the color for the dot that you want to draw
paint.setColor(Color.parseColor("#fd583f"));
//function to create dot
createDot(canvas,paint);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//Animation called when attaching to the window, i.e to your screen
startAnimation();
}
private void createDot(Canvas canvas, Paint paint) {
//here i have setted progress bar with 10 dots , so repeat and wnen i = mDotPosition then increase the radius of dot i.e mBounceDotRadius
for(int i = 0; i < mDotAmount; i++ ){
if(i == mDotPosition){
canvas.drawCircle(10+(i*20), mBounceDotRadius, mBounceDotRadius, paint);
}else {
canvas.drawCircle(10+(i*20), mBounceDotRadius, mDotRadius, paint);
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
//calculate the view width
int calculatedWidth = (20*9);
width = calculatedWidth;
height = (mBounceDotRadius*2);
//MUST CALL THIS
setMeasuredDimension(width, height);
}
private void startAnimation() {
BounceAnimation bounceAnimation = new BounceAnimation();
bounceAnimation.setDuration(100);
bounceAnimation.setRepeatCount(Animation.INFINITE);
bounceAnimation.setInterpolator(new LinearInterpolator());
bounceAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
mDotPosition++;
//when mDotPosition == mDotAmount , then start again applying animation from 0th positon , i.e mDotPosition = 0;
if (mDotPosition == mDotAmount) {
mDotPosition = 0;
}
Log.d("INFOMETHOD","----On Animation Repeat----");
}
});
startAnimation(bounceAnimation);
}
private class BounceAnimation extends Animation {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//call invalidate to redraw your view againg.
invalidate();
}
}
}
snap shot:
I have used the class HorizontalDottedProgress - this is a real solution, but it sometimes draws very small dots. Also this widget doesn't react on setVisibility(Visibility.GONE) and can't be hidden after showing.
That's why I slightly modified (and renamed for myself) this class. Dot sizes and distances are calculated using screen density now. Function onDraw() checks isShown() before drawing.
Then, I've added a possibility to specify some properties (such as color, count and timeout) in layout. In my project I use them in the following manner:
<my.domain.tools.ToolDotProgress
android:id="#+id/dots_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:color="#color/colorAccent"
app:count="5"
app:timeout="300" />
To declare these properties I've added to file res/values/attrs.xml the following code:
<declare-styleable name="ToolDotProgress">
<attr name="color" format="color" />
<attr name="count" format="integer" />
<attr name="timeout" format="integer" />
</declare-styleable>
For more information read the manual: https://developer.android.com/training/custom-views/create-view.html
Here is my variant of this class:
public class ToolDotProgress extends View {
// distance between neighbour dot centres
private int mDotStep = 20;
// actual dot radius
private int mDotRadius = 5;
// Bounced Dot Radius
private int mBigDotRadius = 8;
// to get identified in which position dot has to bounce
private int mDotPosition;
// specify how many dots you need in a progressbar
private static final int MIN_COUNT = 1;
private static final int DEF_COUNT = 10;
private static final int MAX_COUNT = 100;
private int mDotCount = DEF_COUNT;
private static final int MIN_TIMEOUT = 100;
private static final int DEF_TIMEOUT = 500;
private static final int MAX_TIMEOUT = 3000;
private int mTimeout = DEF_TIMEOUT;
private int mDotColor = Color.parseColor("#fd583f");
public ToolDotProgress(Context context) {
super(context);
initDotSize();
}
public ToolDotProgress(Context context, AttributeSet attrs) {
super(context, attrs);
initDotSize();
applyAttrs(context, attrs);
}
public ToolDotProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDotSize();
applyAttrs(context, attrs);
}
private void initDotSize() {
final float scale = getResources().getDisplayMetrics().density;
mDotStep = (int)(mDotStep * scale);
mDotRadius = (int)(mDotRadius * scale);
mBigDotRadius = (int)(mBigDotRadius * scale);
}
private void applyAttrs(Context context, AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.ToolDotProgress, 0, 0);
try {
mDotColor = a.getColor(R.styleable.ToolDotProgress_color, mDotColor);
mDotCount = a.getInteger(R.styleable.ToolDotProgress_count, mDotCount);
mDotCount = Math.min(Math.max(mDotCount, MIN_COUNT), MAX_COUNT);
mTimeout = a.getInteger(R.styleable.ToolDotProgress_timeout, mTimeout);
mTimeout = Math.min(Math.max(mTimeout, MIN_TIMEOUT), MAX_TIMEOUT);
} finally {
a.recycle();
}
}
//Method to draw your customized dot on the canvas
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isShown()) {
Paint paint = new Paint();
paint.setColor(mDotColor);
createDots(canvas, paint);
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
private void createDots(Canvas canvas, Paint paint) {
for (int i = 0; i < mDotCount; i++ ) {
int radius = (i == mDotPosition) ? mBigDotRadius : mDotRadius;
canvas.drawCircle(mDotStep / 2 + (i * mDotStep), mBigDotRadius, radius, paint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// MUST CALL THIS
setMeasuredDimension(mDotStep * mDotCount, mBigDotRadius * 2);
}
private void startAnimation() {
BounceAnimation bounceAnimation = new BounceAnimation();
bounceAnimation.setDuration(mTimeout);
bounceAnimation.setRepeatCount(Animation.INFINITE);
bounceAnimation.setInterpolator(new LinearInterpolator());
bounceAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
if (++mDotPosition >= mDotCount) {
mDotPosition = 0;
}
}
});
startAnimation(bounceAnimation);
}
private class BounceAnimation extends Animation {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
// call invalidate to redraw your view again
invalidate();
}
}
}
Change onDraw() method to:
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
if(isShown){
Paint paint = new Paint();
//set the color for the dot that you want to draw
paint.setColor(Color.parseColor("#fd583f"));
//function to create dot
createDot(canvas,paint);
}
}

Get current RotationAngle while Animation is running on Android

I'm rotating an ImageView by using an Animation defined by a XML file.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<rotate
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="90"
android:fillAfter="true" />
</set>
Although ImageView has the getRotation() method , it only returns the first value that has been set to the image object.
Is there anyway to get the current rotation degrees by using a XML animation? If not, what should be the best way to go with it?
public class CustomImageView extends ImageView {
private ObjectAnimator rotationAnimator;
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setRotation(float rotation) {
super.setRotation(rotation);
// Do something
}
public synchronized void startAnimation(int animationDuration) {
if (rotationAnimator == null || !rotationAnimator.isRunning()) {
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf2 = Keyframe.ofFloat(0.5f, 180f);
Keyframe kf1 = Keyframe.ofFloat(1f, 360f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
rotationAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhRotation);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(animationDuration);
rotationAnimator.start();
}
else {
// Already running
}
}
Version 2, since you are having problems
public class CustomImageView extends ImageView {
private ObjectAnimator rotationAnimator;
private float myRotation;
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setMyRotation(float rotation) {
this.myRotation = rotation;
Log.d("CustomImage", "Rotation: " + rotation);
invalidate();
}
public synchronized void startAnimation(int animationDuration) {
if (rotationAnimator == null || !rotationAnimator.isRunning()) {
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf2 = Keyframe.ofFloat(0.5f, 180f);
Keyframe kf1 = Keyframe.ofFloat(1f, 360f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("myRotation", kf0, kf1, kf2);
rotationAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhRotation);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(animationDuration);
rotationAnimator.start();
}
else {
// Already running
}
}
public synchronized void stopAnimation() {
if (rotationAnimator != null) {
rotationAnimator.cancel();
rotationAnimator = null;
}
}
public synchronized boolean getAnimationRunning() {
return rotationAnimator != null && rotationAnimator.isRunning();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.rotate(myRotation, getWidth() / 2.0f, getHeight() / 2.0f);
super.onDraw(canvas);
}
}
Sample logcat output:
04-22 22:48:55.475 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 358.44
04-22 22:48:55.490 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 0.48000813
04-22 22:48:55.505 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 2.4
04-22 22:48:55.525 16341-16341/animation.example.com.animationdemo D/CustomImage﹕ Rotation: 4.44
I have packed the code into a simple project: LINK

Change ViewPager animation duration when sliding programmatically

I'm changing slide with the following code:
viewPager.setCurrentItem(index++, true);
But it changes too fast. Is there a way to set manually the animation speed?
I've wanted to do myself and have achieved a solution (using reflection, however). I haven't tested it yet but it should work or need minimal modification. Tested on Galaxy Nexus JB 4.2.1. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:
ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow
ViewPagerCustomDuration.java:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
import java.lang.reflect.Field;
public class ViewPagerCustomDuration extends ViewPager {
public ViewPagerCustomDuration(Context context) {
super(context);
postInitViewPager();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}
private ScrollerCustomDuration mScroller = null;
/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void postInitViewPager() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
}
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
}
ScrollerCustomDuration.java:
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
public class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 1;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
#SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}
}
I have found better solution, based on #df778899's answer and the
Android ValueAnimator API. It works fine without reflection and is very flexible.
Also there is no need for making custom ViewPager and putting it into android.support.v4.view package.
Here is an example:
private void animatePagerTransition(final boolean forward) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private int oldDragPosition = 0;
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
if (viewPager.beginFakeDrag()) {
animator.start();
}
}
UPDATE:
Just checked if this solution can be used to swipe several pages at once (for example if first page should be showed after the last one). This is slightly modified code to handle specified page count:
private int oldDragPosition = 0;
private void animatePagerTransition(final boolean forward, int pageCount) {
// if previous animation have not finished we can get exception
if (pagerAnimation != null) {
pagerAnimation.cancel();
}
pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
if (viewPager.beginFakeDrag()) { // checking that started drag correctly
pagerAnimation.start();
}
}
private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
viewPager.endFakeDrag();
oldDragPosition = 0;
viewPager.beginFakeDrag();
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
animator.setRepeatCount(pageCount);
return animator;
}
public class PresentationViewPager extends ViewPager {
public static final int DEFAULT_SCROLL_DURATION = 250;
public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;
public PresentationViewPager (Context context) {
super(context);
}
public PresentationViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDurationScroll(int millis) {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new OwnScroller(getContext(), millis));
} catch (Exception e) {
e.printStackTrace();
}
}
public class OwnScroller extends Scroller {
private int durationScrollMillis = 1;
public OwnScroller(Context context, int durationScroll) {
super(context, new DecelerateInterpolator());
this.durationScrollMillis = durationScroll;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis);
}
}
}
Better solution is to simply access the private fields by creating the class in the support package. EDIT This is bound to the MAX_SETTLE_DURATION of 600ms, set by the ViewPagerclass.
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class SlowViewPager extends ViewPager {
// The speed of the scroll used by setCurrentItem()
private static final int VELOCITY = 200;
public SlowViewPager(Context context) {
super(context);
}
public SlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
}
}
You can, of course, then add a custom attribute so this can be set via XML.
Here is my code used in Librera Reader
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initMyScroller();
}
private void initMyScroller() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext())); // my liner scroller
Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.set(this, Dips.DP_10);//10 dip
Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.set(this, 0); //0 velocity
} catch (Exception e) {
LOG.e(e);
}
}
public class MyScroller extends Scroller {
public MyScroller(Context context) {
super(context, new LinearInterpolator()); // my LinearInterpolator
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, 175);//175 duration
}
}
}
I used Cicero Moura's version to make a Kotlin class that still works perfectly as of Android 10.
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager
class CustomViewPager(context: Context, attrs: AttributeSet) :
ViewPager(context, attrs) {
private companion object {
const val DEFAULT_SPEED = 1000
}
init {
setScrollerSpeed(DEFAULT_SPEED)
}
var scrollDuration = DEFAULT_SPEED
set(millis) {
setScrollerSpeed(millis)
}
private fun setScrollerSpeed(millis: Int) {
try {
ViewPager::class.java.getDeclaredField("mScroller")
.apply {
isAccessible = true
set(this#CustomViewPager, OwnScroller(millis))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}
Initializing from the activity class:
viewPager.apply {
scrollDuration = 2000
adapter = pagerAdapter
}
After wasting my whole day I found a solution set offscreenPageLimit to total no. of the page.
In order to keep a constant length ViewPager scrolls smooth, setOffScreenLimit(page.length) will keep all the views in memory. However, this poses a problem for any animations that involves calling View.requestLayout function (e.g. any animation that involves making changes to the margin or bounds). It makes them really slow (as per Romain Guy) because the all of the views that's in memory will be invalidated as well. So I tried a few different ways to make things smooth but overriding requestLayout and other invalidate methods will cause many other problems.
A good compromise is to dynamically modify the off screen limit so that most of the scrolls between pages will be very smooth while making sure that all of the in page animations smooth by removing the views when the user. This works really well when you only have 1 or 2 views that will have to make other views off memory.
***Use this when no any solution works because by setting offeset limit u will load all the fragments at the same time

Automatic horizontal scroll in TextView

I have custom gallery.
Gallery represents items that are frame layout.
There are one imageView and textView above it.
If text in textView is too long, i need it to be scrolled automatically.
It's one line of text, and it's needed to be scrolled horizontally.
I've found this snippet of code:
TextView
android:text="Single-line text view that scrolls automatically"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit ="marquee_forever"
android:focusable="true"
android:focusableInTouchMode="true"
android:scrollHorizontally="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
It works in my test app with only one text view in it.
But it doesn't work in my gallery. Noting happens, text just stay still.
Any help?
Try this custom TextView class:
public class AutoScrollingTextView extends TextView {
public AutoScrollingTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public AutoScrollingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoScrollingTextView(Context context) {
super(context);
}
#Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
#Override
public void onWindowFocusChanged(boolean focused) {
if (focused) {
super.onWindowFocusChanged(focused);
}
}
#Override
public boolean isFocused() {
return true;
}
}
and set the following XML attributes:
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
This works beautifully in my dictionary apps where multiple entries may need to auto-scroll simultaneously to display complete content.
The marquee effect on a TextView is only designed to work when the view is focused or selected. The XML code you have tries to make the TextView focused all the time. Unfortunately, since only one view can be focused at any time, and since you have multiple views in the gallery, this approach will not work for you.
The easiest way to accomplish this otherwise is to make the TextViews always be selected. Multiple TextViews can hold the selected state at one time. Selection is meant to be used for an active element of an AdapterView, but still works outside of one. Firstly, remove the attributes modifying the focus from the XML and then just call TextView.setSelected(true) sometime after the view is initialised, e.g. in Activity.onCreate(Bundle) (there is no XML attribute for this). If you are supplying the views from an adapter, then you can call TextView.setSelected(true) during the getView() method after you inflate the view.
Here is an example project showing marquee working for multiple TextViews, and the behaviour inside a Gallery.
Try using ViewPager instead of gallery. This is available in android support packages. http://android-developers.blogspot.in/2011/08/horizontal-view-swiping-with-viewpager.html
I've tried everything, and finally came up with this. This works for me...hope that this will help you someday. Cheers.
package com.gui.custom_views;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.media_player.AndroidMediaPlayerActivity;
/**
* Custom Automatic Scrollable Text View
*
* #author Veljko Ilkic
*
*/
public class AutomaticScrollTextView extends LinearLayout {
// Context of application
Context context;
// TextView
private TextView mTextField1;
// Horizontal scroll
private ScrollView mScrollView1;
// Animation on start
private Animation mMoveTextOnStart = null;
// Out animation
private Animation mMoveText1TextOut = null;
// Duration of animation on start
private int durationStart;
// Duration of animation
private int duration;
// Pain for drawing text
private Paint mPaint;
// Text current width
private float mText1TextWidth;
/**
* Control the speed. The lower this value, the faster it will scroll.
*/
public static final int MS_PER_PX = 80;
/**
* Control the pause between the animations. Also, after starting this
* activity.
*/
public static final int PAUSE_BETWEEN_ANIMATIONS = 0;
private boolean mCancelled = false;
// Layout width
private int mWidth;
// Animation thread
private Runnable mAnimation1StartRunnable;
public AutomaticScrollTextView(Context context) {
super(context);
init(context);
this.context = context;
}
public AutomaticScrollTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
this.context = context;
}
private void init(Context context) {
initView(context);
// init helper
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(1);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mWidth = getMeasuredWidth();
// Calculate
prepare();
// Setup
setupText1Marquee();
}
#Override
public void setOnClickListener(OnClickListener l) {
super.setOnClickListener(l);
mTextField1.setOnClickListener(l);
}
// Method to finally start the marquee.
public void startMarquee() {
prepare();
prepareTextFields();
startTextField1Animation();
mCancelled = false;
}
private void startTextField1Animation() {
mAnimation1StartRunnable = new Runnable() {
public void run() {
mTextField1.setVisibility(View.VISIBLE);
mTextField1.startAnimation(mMoveTextOnStart);
}
};
postDelayed(mAnimation1StartRunnable, PAUSE_BETWEEN_ANIMATIONS);
}
public void reset() {
mCancelled = true;
if (mAnimation1StartRunnable != null) {
removeCallbacks(mAnimation1StartRunnable);
}
mTextField1.clearAnimation();
prepareTextFields();
mMoveTextOnStart.reset();
mMoveText1TextOut.reset();
mScrollView1.removeView(mTextField1);
mScrollView1.addView(mTextField1);
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
invalidate();
}
public void prepareTextFields() {
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
mTextField1.setVisibility(View.INVISIBLE);
expandTextView(mTextField1);
}
private void setupText1Marquee() {
// Calculate duration of animations
durationStart = (int) ((mWidth + mText1TextWidth) * MS_PER_PX);
duration = (int) (2 * mWidth * MS_PER_PX);
// On start animation
mMoveTextOnStart = new TranslateAnimation(0, -mWidth - mText1TextWidth,
0, 0);
mMoveTextOnStart.setDuration(durationStart);
mMoveTextOnStart.setInterpolator(new LinearInterpolator());
mMoveTextOnStart.setFillAfter(true);
// Main scrolling animation
mMoveText1TextOut = new TranslateAnimation(mWidth, -mWidth
- mText1TextWidth, 0, 0);
mMoveText1TextOut.setDuration(duration);
mMoveText1TextOut.setInterpolator(new LinearInterpolator());
mMoveText1TextOut.setFillAfter(true);
mMoveText1TextOut.setRepeatCount(Animation.INFINITE);
// Animation listeners
mMoveTextOnStart
.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
invalidate();
mTextField1.invalidate();
}
public void onAnimationEnd(Animation animation) {
if (mCancelled) {
return;
}
mTextField1.startAnimation(mMoveText1TextOut);
}
public void onAnimationRepeat(Animation animation) {
invalidate();
mTextField1.invalidate();
}
});
mMoveText1TextOut
.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
invalidate();
mTextField1.invalidate();
}
public void onAnimationEnd(Animation animation) {
if (mCancelled) {
return;
}
}
public void onAnimationRepeat(Animation animation) {
invalidate();
mTextField1.invalidate();
}
});
}
private void prepare() {
// Measure
mPaint.setTextSize(mTextField1.getTextSize());
mPaint.setTypeface(mTextField1.getTypeface());
mText1TextWidth = mPaint.measureText(mTextField1.getText().toString());
setupText1Marquee();
}
private void initView(Context context) {
setOrientation(LinearLayout.VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, Gravity.LEFT));
setPadding(0, 0, 0, 0);
// Scroll View 1
LayoutParams sv1lp = new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT);
sv1lp.gravity = Gravity.CENTER_HORIZONTAL;
mScrollView1 = new ScrollView(context);
// Scroll View 1 - Text Field
mTextField1 = new TextView(context);
mTextField1.setSingleLine(true);
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
mTextField1.setTypeface(null, Typeface.BOLD);
mScrollView1.addView(mTextField1, new ScrollView.LayoutParams(
mTextField1.getWidth(), LayoutParams.WRAP_CONTENT));
addView(mScrollView1, sv1lp);
}
public void setText1(String text) {
String temp = "";
if (text.length() < 10) {
temp = " " + text + " ";
} else {
temp = text;
}
mTextField1.setText(temp);
}
public void setTextSize1(int textSize) {
mTextField1.setTextSize(textSize);
}
public void setTextColor1(int textColor) {
mTextField1.setTextColor(textColor);
}
private void expandTextView(TextView textView) {
ViewGroup.LayoutParams lp = textView.getLayoutParams();
lp.width = AndroidMediaPlayerActivity.getScreenWidth();
textView.setLayoutParams(lp);
}
}
I came across this problem once and finally fixed the problem by calling .setFocus() on the textView.
Hi You have Tag in the xml file itself. And also use the Scrollview Property of FOCUS_DOWN in the java file ... Hope It helps to u ...
This code is working properly for me.
scrollview=(ScrollView)findViewById(R.id.scrollview1);
tb2.setTextSize(30);
tb2.setMovementMethod(new ScrollingMovementMethod());
scrollview.post(new Runnable() {
public void run() {
scrollview.fullScroll(View.FOCUS_DOWN);
}
});
public class ScrollingTextView extends TextView {
public ScrollingTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public ScrollingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollingTextView(Context context) {
super(context);
}
#Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
#Override
public void onWindowFocusChanged(boolean focused) {
if (focused) {
super.onWindowFocusChanged(focused);
}
}
#Override
public boolean isFocused() {
return true;
}
}
<com.test.autoscroll.ScrollingTextView
android:id="#+id/actionbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:textSize="16dip"
android:textStyle="bold"
android:lines="1"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:text="autoscrollable textview without focus to textview...working...."
android:marqueeRepeatLimit="marquee_forever"
/>
Add this code to your own
findViewById(R.id.yourtextviewid).setSelected(true);
maybe your problem is fixed.

Categories

Resources