ListView回收切换seekbar位置

ListView recycling switches seekbar position

我有一个奇怪的问题。 我有一个自定义 ListViewBaseAdapter。在我的 ListView 行布局中,我有几个 TextViewButtons 和一个 SeekBar。一切都很好,除了回收之外没有任何问题。

发生了什么: 所有 SeekBar 都是隐藏的,只有一个可见。 SeekBar 在行 MediaPlayer 播放时可见。那部分也很好用。

但是,当用户向上或向下滚动时,可见 SeekBar 的行不可见时,它会回收,SeekBar 也会被回收,即使不可见也会不断更新那一行没有播放(mp)。当用户滚动回 return 到正在播放的视图时,SeekBar 是可见的,但没有更新,它的位置是 0。取而代之的是随机 SeekBar 正在更新,但它不可见(我确实在所有 SeekBar 都可见时进行了测试,所以我知道会发生这种情况)

当然我可以做最愚蠢的解决方案并禁用 ListView 回收,但这会使用户体验非常糟糕,并且可能使应用程序 运行 内存不足,并且使用 LargeHeap 是蹩脚的。 所以下面的方法是没有问题的。

@Override
public int getViewTypeCount() {
    return getCount();
}

@Override
public int getItemViewType(int position) {
    return position;
}

我的问题:有什么明显的解决办法吗? 如何只保留一排不被回收? 除非真的有必要,否则我不会 post 编写代码,代码太大了。相反,我只会 post 与问题相关的重要部分:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    View row = convertView;
    holder = null;
    if (row == null) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = inflater.inflate(R.layout.item, parent, false);
        holder = new Ids(row);
        row.setTag(holder);
    } else {
        holder = (Ids) row.getTag();
    }

    rowItemClass = (ListViewRow) getItem(position);
    if (Globals.isPlaying && Globals.pos == position) {        
        holder.seekbar.setVisibility(View.VISIBLE);
    } else {
        holder.seekbar.setVisibility(View.GONE);    
    }

    holder.seekbar.setTag(position);
    final Ids finalHolder = holder;

    ...
    OnClickListener{....
    Globals.mp.start();
    finalHolder.seekbar.setProgress(0);
    finalHolder.seekbar.setMax(Globals.mp.getDuration());
    ..

    updateTimeProgressBar = new Runnable() {
        @Override
        public void run() {
            if (Globals.isPlaying) {  
                finalHolder.seekbar.setProgress(Globals.mp.getCurrentPosition());
                int currentPosition = Globals.mp.getCurrentPosition();
                handler.postDelayed(this, 100);
            }
        }
    };

    handler.postDelayed(updateTimeProgressBar, 100);
    finalHolder.seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            handler.removeCallbacks(updateTimeProgressBar);
            Globals.mp.seekTo(finalHolder.seekbar.getProgress());
            int currentPosition = Globals.mp.getCurrentPosition();
            handler.postDelayed(updateTimeProgressBar, 500);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            handler.removeCallbacks(updateTimeProgressBar);
        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // Intentionally left empty
        }
    });
}

就是这样。 为什么会这样? 有没有办法禁用一行的回收? 更新 ListView 中的所有 SeekBar 是不是一个坏主意,因为只有 1 mp 会播放?这对性能有多糟糕?

我将概述解决此问题的最佳方法之一(当然是我认为),同时尊重 ListView's 回收能力。

但首先,这 必须去 :

@Override
public int getViewTypeCount() {
    return getCount();
}

@Override
public int getItemViewType(int position) {
    return position;
}

解法:

Subclass SeekBar 并让它处理所有更新:

  • 定义两个方法来标记this SeekBar active & inactive .

  • 标记SeekBar活动时,提供当前位置(进度)。此外,post Runnable 将更新此 SeekBar

  • 标记 SeekBar 无效 时,只需删除 Runnable 的回调并收工

在您的 Adapter 中,您需要保持的唯一状态是跟踪当前播放的歌曲。您可以通过跟踪 ListView 看到的 位置 或保持当前播放曲目的 ID 来做到这一点(当然,这,仅当您的模型使用 ID 时才有效)。

像这样:

// Define `setActiveAtPosition(int position)` and `setInactive()` 
// in your custom SeekBar

if (Globals.isPlaying && Globals.pos == position) {   
    // Define `setActiveAtPosition(int position)` in your custom SeekBar     
    holder.seekbar.setActiveAtPosition(Globals.mp.getCurrentPosition());
    holder.seekbar.setVisibility(View.VISIBLE);
} else {
    holder.seekbar.setInactiveForNow();
    holder.seekbar.setVisibility(View.GONE);    
}

请求代码:

自定义搜索栏class实施:

public class SelfUpdatingSeekbar extends SeekBar implements SeekBar.OnSeekBarChangeListener {

    private boolean mActive = false;

    private Runnable mUpdateTimeProgressBar = new Runnable() {
        @Override
        public void run() {
            if (Globals.isPlaying) {
                setProgress(Globals.mp.getCurrentPosition());
                postDelayed(this, 100L);
            }
        }
    };

    public SelfUpdatingSeekbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnSeekBarChangeListener(this);
    }

    public void setActive() {
        if (!mActive) {
            mActive = true;
            removeCallbacks(mUpdateTimeProgressBar);
            setProgress(Globals.mp.getCurrentPosition());
            setVisibility(View.VISIBLE);
            postDelayed(mUpdateTimeProgressBar, 100L);
        }
    }

    public void setInactive() {
        if (mActive) {
            mActive = false;
            removeCallbacks(mUpdateTimeProgressBar);
            setVisibility(View.INVISIBLE);
        }
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        removeCallbacks(mUpdateTimeProgressBar);
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        removeCallbacks(mUpdateTimeProgressBar);
        Globals.mp.seekTo(getProgress());
        postDelayed(mUpdateTimeProgressBar, 500);
    }
}

在您的适配器中 getView(...):

if (Globals.isPlaying && Globals.pos == position) {        
    holder.seekbar.setActive();
} else {
    holder.seekbar.setInactive();    
}

不用说,您将需要在 xml 布局中使用此自定义实现:

<com.your.package.SelfUpdatingSeekbar
    ....
    .... />