每个列表项中都有旋转进度条

13

我已经为此苦苦思考了很长时间,并寻找答案,但没有任何运气!虽然这似乎是微不足道的问题,但据我所知,它并非如此。

在我的Android应用程序中使用了一个列表视图(listview),其中每个项目(view)显示一个旋转的进度条(ProgressBar),在加载和显示内容之前(内容通过http调用和json检索,因此处理可能需要一段时间)。问题是旋转进度条彼此独立旋转,从而创建旋转混沌而不是同步排列的漂亮加载标记的效果。

我已经尝试了我能想到的所有方法...在getView()中不回收ProgressBar...只有同一个ProgressBar实例...每当列表项通过活动中的onScroll()变得可见时重置ProgressBar的android:progress属性...等等,但由于它们在创建时开始旋转 (当适配器中的getView被调用时),它们将永远不会具有相同的周期同步。

输入图像描述

欢迎任何建议!


1
很好的问题!我自己也很好奇。 - st0le
可能是因为在屏幕上逐个绘制列表项,所以第一个列表项的进度条最先启动。 - user370305
@user370305 这已经在问题本身中解释了。 - Andro Selva
我的上一个回答有点小问题,现在已经修复好了! - Oleg Vaskevich
6个回答

2

编辑:现在已经完美解决! - 在第一次重复后插入动画监听器调用setStartOffset(),使其不会随机“跳动”。


我找到了一个可行的解决方案,它通过计时将动画与当前系统毫秒数同步。这有点像黑客技巧,因为它使用反射来获取ProgressBar中的mAnimation字段。尽管如此,自从创建以来,这个字段在Android源代码中一直存在(适用于4.2)。

创建android.widget.SyncedProgressBar类,并在.xml文件中使用它代替ProgressBar。它基本上使动画从动画持续时间的开始处开始。您还可以尝试使用setDuration(500)来验证它是否有效(进度轮将非常快地旋转)。希望这能帮助到您!

package android.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;

import java.lang.reflect.Field;

/**
* @author Oleg Vaskevich
*
*/
public class SyncedProgressBar extends ProgressBar {

    public SyncedProgressBar(Context context) {
        super(context);
        modifyAnimation();
    }

    public SyncedProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        modifyAnimation();
    }

    public SyncedProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        modifyAnimation();
    }

    @Override
    public void setVisibility(int v) {
        super.setVisibility(v);
        modifyAnimation();
    }

    @SuppressLint("NewApi")
    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        modifyAnimation();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        modifyAnimation();
    }

    @Override
    public synchronized void setIndeterminate(boolean indeterminate) {
        super.setIndeterminate(indeterminate);
        modifyAnimation();
    }

    public void modifyAnimation() {
        Field mAnim;
        try {
            mAnim = ProgressBar.class.getDeclaredField("mAnimation");
            mAnim.setAccessible(true);
            AlphaAnimation anim = (AlphaAnimation) mAnim.get(this);
            if (anim == null)
                return;

            // set offset to that animations start at same time
            long duration = anim.getDuration();
            long timeOffset = System.currentTimeMillis() % duration;
            anim.setDuration(duration);
            anim.setStartOffset(-timeOffset);
            anim.setAnimationListener(new AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                    animation.setStartOffset(0);
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                }
            });
        } catch (Exception e) {
            Log.d("SPB", "that didn't work out...", e);
            return;
        }
        postInvalidate();
    }

}

1

以下是如何使其工作的方法。只使用一个AnimationDrawable,多路复用回调。 ProgressBar并没有真正添加任何东西,您可以坚持使用View。 创建一个绕过Drawable管理的View子类。

public class Whirly extends View
{
   Drawable image = null;

   public Whirly(Context context, AttributeSet attr)
   {
      super(context, attr);
   }

   @Override
   public void draw(Canvas canvas)
   {
      if (image!=null)
         image.draw(canvas);
   }

   public void setImageDrawable(Drawable image)
   {
      this.image = image;
      invalidate();
   }
}

然后让您的活动以某种方式跟踪所有旋转物件。

public class Demo extends Activity implements Drawable.Callback
{
   private Handler h;
   private AnimationDrawable a;

   private Whirly w0;
   private Whirly w1;
   private Whirly w2;
   private Whirly w3;

   public void onCreate(Bundle bundle)
   {
      ...

      h = new Handler();

      a = (AnimationDrawable) getResources().getDrawable(R.drawable.mywhirly);
      int width = a.getIntrinsicWidth();
      int height = a.getIntrinsicHeight();
      a.setBounds(0, 0, width, height);

      w0 = (Whirly) findViewById(R.id.whirly0);
      w1 = (Whirly) findViewById(R.id.whirly1);
      w2 = (Whirly) findViewById(R.id.whirly2);
      w3 = (Whirly) findViewById(R.id.whirly3);

      w0.setImageDrawable(a);
      w1.setImageDrawable(a);
      w2.setImageDrawable(a);
      w3.setImageDrawable(a);

      a.setCallback(this);
      a.start();
   }

   public void invalidateDrawable (Drawable who)
   {
      w0.invalidate();
      w1.invalidate();
      w2.invalidate();
      w3.invalidate();
   }

   public void scheduleDrawable (Drawable who, Runnable what, long when)
   {
      h.postAtTime(what, who, when);
   }

   public void unscheduleDrawable (Drawable who, Runnable what)
   {
      h.removeCallbacks(what, who);
   }
}

我已经在处理你的代码一段时间了,看起来进展不错,至少方向是正确的 :) 但问题是,我在我的适配器中实现这个代码时遇到了困难。在你的示例中,每个“whirly”都已经在某个资源中定义,并且它们都设置为使用相同的AnimationDrawable。我想只定义一个“whirly”,让我的ListView中的每个列表项共享它。 - maglar
在您的ListView中,每个项目都必须具有自己的视图布局。对于每个动画图形,您都需要一个视图。Whirly只是最便宜的视图(View)周围非常薄的包装器。项目布局XML将只有一个水平LinearLayout,一个用于“Tues 1”的TextView和一个Whirly。当项目完成时,您将使用whirly.setImageDrawable(null)并将其从回调列表中删除。onResume将a.start(),onSuspend将a.stop(),当回调列表上的Whirlys计数为零时,a.stop()。 - Renate

0
在您的适配器getView()中禁用您的progressView,然后对于每个progressView。
handler.postAtTime(new Runnable(){

public void run(){
     progressView.setEnabled(true);
}}, someTimeInTheFuture
);

这样做的效果是同时启用所有的progressViews。这可能有效,但我还没有测试过。如果这不起作用,那么您可能需要动态添加progressView(不包括在布局中,而是通过addView()添加)。但是要在runnable中执行此操作,关键在于它们都同时被添加。


0
挖掘源代码后,我发现旋转图像是一个可绘制对象,由于私有的Animation而旋转。没有办法获取视图或动画,因此放弃了我重用所有视图上的一个动画的想法。我会说你唯一的选择是Jug6ernaut建议的,尽管它不是最美观的解决方案。

getIndeterminateDrawable在任何情况下都能正常工作。链接 - Renate

0

我不确定这是否可能,因为当ListView中的每一行移出屏幕时,它都会被回收。当新行出现在屏幕上时,ProgressBar视图将需要重新与彼此同步。保持所有行同步将是一件很麻烦的事情。

您是否考虑过在一个请求中加载所有列表数据并将该数据缓存到本地存储中?这将更有效,并且具有更好的用户体验。拥有立即加载空行的ListView没有什么用处。我宁愿等待列表完全填充。


不是太麻烦,看看我的答案。 - Oleg Vaskevich

0

1> 为列表行创建布局

<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="10dip" >

    <TextView
        android:id="@+id/word_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:text="Word 1" />

    <ProgressBar
        android:id="@+id/row_progress"
        style="@style/Widget.Sherlock.Light.ProgressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:indeterminate="true" />

    <ImageView
        android:id="@+id/speaker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/speaker_icon" 
        android:visibility="gone"/>
</RelativeLayout>

2> 然后在getView方法中

if(word.getIsWordSynchedLocally() == 1){
                convertView.findViewById(R.id.row_progress).setVisibility(View.GONE);
                convertView.findViewById(R.id.speaker).setVisibility(View.VISIBLE);
            }else{
                convertView.findViewById(R.id.row_progress).setVisibility(View.VISIBLE);
                convertView.findViewById(R.id.speaker).setVisibility(View.GONE);
            }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接