【问题标题】:Update GridView/ListView without re-populating在不重新填充的情况下更新 GridView/ListView
【发布时间】:2014-08-19 00:22:32
【问题描述】:

每当您更新 GridView/ListView 时,您都会在适配器上调用 notifyDatasetChanged,该适配器会使用新数据重新填充列表,从而删除当前列表中的所有内容。

现在以video 为例,以L Preview 设计指南为例

是否有可能实现同样的效果,您看到的唯一变化是新项目在适配器重新加载时进入而没有任何“闪烁”,还是现在只能在 L 中完成?

这个效果也可以在Google Keep应用添加新笔记时发现

【问题讨论】:

标签: android android-listview android-gridview android-adapter


【解决方案1】:

恐怕这都是视觉上的诡计 - 也就是动画。

...删除当前列表中的所有内容。

其实没有。

notifyDataSetChanged 仅告诉底层观察者数据已更改。而已。作为响应,每个可见视图调用getView(...) - ListView#getChildCount() 可访问的数字。由于任一索引已更改(添加/删除项目),或者这些索引处的对象中保存的数据已(^)更改(一个或多个项目已更改),因此通过后续调用 ListView#getView(...) 以视觉方式刷新数据(^) .

“Android 开发者”有一个有趣的视频,它解释了如何产生你想要的效果:DevBytes: ListView Cell Insertion Animation。

视频/示例仅讨论单项插入。但它应该可以扩展到具有一定时间和数学技能的所有数据操作。

我能够扩展示例代码以插入多个项目。我将动画更改为更简单的 alpha-fade-in。单击Add row 按钮将随机数(介于 1 和 3 之间)添加到列表视图。这是它的样子:

伪工作流程:

  • 在将新项目传递给适配器之前,遍历列表视图中的所有可见项目并保存它们的边界(在Rect)和快照(在BitmapDrawables)

  • 将项目传递给适配器

  • 将OnPreDrawListener 添加到 ListView 的 ViewTreeObserver。

  • 当onPreDraw(...) 被调用时,新项目就可以被绘制了。我们可以使用带有相应索引的ListView#getChildAt(...) 访问他们的views。

  • 所有新项目都设置为不可见,并分配了 alpha 动画师

  • 所有旧项目都分配了 translation_y 动画师

  • 将不再通过 getChildAt(..) 访问的项目 - 因为添加了新项目 - 被分配了 translation_y 动画师 - 让它们离开屏幕(或超出列表视图边界)

  • translation_y 动画师被解雇。当这些动画师运行完毕后,alpha 动画师就会启动。

请注意,动画的种类(alpha、平移、缩放或这些的任意组合)相对而言无关紧要。我想这在GridView 或StaggeredGridView(keep) 的情况下会更难——只是因为数学将涉及X 和 Y 中的翻译。不过,工作流程应该保持不变。

(^) - 语法不正确,但自然是 IMO。

【讨论】:

  • In response, getView(...) is called for each visible view 这就是我删除列表中所有内容的意思。我有一种感觉,这将涉及创建各种副本并移动该副本。除非其他人提供更好的东西(我对此表示怀疑),否则这可能是让我得到想要的答案。
  • @tyczj ... that is what I meant by removing everything in the list. 哦,好的。我也将等待更好(和/或更简单)的解决方案。这是一个有趣的问题...谢谢。
【解决方案2】:

这可以通过抓取视图并更改其中的数据来实现。我提供了一个示例,在该示例中,您可以在长按时更改数据而不刷新可见项,也无需调用 notifyDataSetChanged()。

此问题已在 Google I/O 2010 上提出,您可以在此处观看:

The world of ListView, time 52:30

代码改编自Vogella

package com.example.stableids;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.os.Build;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@SuppressLint("NewApi")
public class MainActivity extends Activity {

      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @SuppressLint("NewApi")
    @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ListView listview = (ListView) findViewById(R.id.listview);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
            "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
            "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
            "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
            "Android", "iPhone", "WindowsMobile" };

        final ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < values.length; ++i) {
          list.add(values[i]);
        }
        final StableArrayAdapter adapter = new StableArrayAdapter(this,
            android.R.layout.simple_list_item_1, list);
        listview.setAdapter(adapter);

        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

          @SuppressLint("NewApi")
        @Override
          public void onItemClick(AdapterView<?> parent, final View view,
              final int position, long id) {
            final String item = (String) parent.getItemAtPosition(position);
            view.animate().setDuration(2000).alpha(0)
                .withEndAction(new Runnable() {
                  @Override
                  public void run() {
                    int i = list.indexOf(item);

                    list.remove(item);
                    list.add(i, "MODIFIED");
                    adapter.mIdMap.put("MODIFIED", position);
                    TextView tv = (TextView)view.findViewById(android.R.id.text1);
                    tv.setText("MODIFIED");
                    //adapter.notifyDataSetChanged();
                    view.setAlpha(1);
                  }
                });
          }

        });
      }

      private class StableArrayAdapter extends ArrayAdapter<String> {

        HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

        public StableArrayAdapter(Context context, int textViewResourceId,
            List<String> objects) {
          super(context, textViewResourceId, objects);
          for (int i = 0; i < objects.size(); ++i) {
            mIdMap.put(objects.get(i), i);
          }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            Log.d("STABLE ID", "getView called for " + position + " position");
            return super.getView(position, convertView, parent);

        }

        @Override
        public long getItemId(int position) {
          String item = getItem(position);
          return mIdMap.get(item);
        }

        @Override
        public boolean hasStableIds() {
          return true;
        }

      }

}

【讨论】:

    【解决方案3】:

    在不重新填充的情况下更新 GridView/ListView

    如果您每次更新和删除时间都使用相同的数组列表而不创建新的 ArrayList,那么您的 adapter.notifyDatasetChanged() 方法将起作用。 请您每次都使用相同的arrayList,并且不要创建适配器类的新实例

    【讨论】:

    • 我不会每次都创建适配器的新实例并使用相同的列表。正如您在我的问题中看到的那样,我使用notifyDatasetChanged 来更新我的网格
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-02
    • 2023-03-13
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多