小部件中的 ListView 在滚动和调整大小时随机添加项目(嵌套远程视图)

ListView in widget adds randomly items on scrolling and resizing (nested remoteviews)

更新:I created a repository代码更少,更容易理解。

我正在尝试创建一个小部件。我像这里描述的那样做了:

部分有效,但我有一个非常奇怪的错误。我做了一个 screencast,所以更容易理解我的意思: http://c.maysi.de/c6H9

截图

如您所见,有些项目是随机添加的。 (RemoteViews 已添加到另一个 RemoteViews 对象) 调整小部件的大小时也会发生同样的情况。

我在日志中打印出来的东西符合预期。没有错误的数据。滚动时也没有新的日志条目。

这是我的代码:

RemoteViewsFactory:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class MyWidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static ArrayList<Item> items = new ArrayList<>();
    private static int itemnr = 0;
    private static int subitemnr = 0;
    private int appWidgetId;
    private Context context;

    public MyWidgetViewsFactory(Context context, Intent intent) {
        this.context = context;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);


        //Some random data to display
        for (int i = 0; i < 10; i++) {
            Item item = new Item(String.valueOf(itemnr++));

            for (int j = 0; j < 3; j++) {
                String[] subitem = {String.valueOf(subitemnr++), String.valueOf(subitemnr++), String.valueOf(subitemnr++)};
                item.addSubitem(subitem);
            }

            items.add(item);
        }
    }

    @Override
    public void onCreate() {
        // no-op
    }

    @Override
    public void onDestroy() {
        // no-op
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public RemoteViews getViewAt(int position) {
        Log.d("MyWidgetViewsFactory", "getViewAt(" + position + "):" + items.get(position));
        Item item = items.get(position);

        RemoteViews itemView = new RemoteViews(context.getPackageName(), R.layout.widget_listview_item);

        itemView.setTextViewText(R.id.textView_itemnr, item.getItemNr());

        for (String[] s : item.getSubitems()) {
            Log.d("MyWidgetViewsFactory", "subitem:" + s[0] + "|" + s[1] + "|" + s[2]);
            RemoteViews subitem = new RemoteViews(context.getPackageName(), R.layout.widget_listview_subitem);

            subitem.setTextViewText(R.id.textView_1, s[0]);
            subitem.setTextViewText(R.id.textView_2, s[1]);
            subitem.setTextViewText(R.id.textView_3, s[2]);

            itemView.addView(R.id.linearLayout_item_body, subitem);
        }
        return itemView;
    }

    @Override
    public RemoteViews getLoadingView() {
        return (null);
    }

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

    @Override
    public long getItemId(int position) {
        return (position);
    }

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

    @Override
    public void onDataSetChanged() {
        // no-op
    }

    class Item {
        private ArrayList<String[]> subitems = new ArrayList<>();
        private String itemnr = "";

        Item(String itemnr) {
            this.itemnr = itemnr;
        }

        Item() {
        }

        public void addSubitem(String[] subitem) {
            this.subitems.add(subitem);
        }

        public ArrayList<String[]> getSubitems() {
            return subitems;
        }

        public String getItemNr() {
            return itemnr;
        }

        public void setItemNr(String itemnr) {
            this.itemnr = itemnr;
        }
    }
}

AppWidgetProvider

    public class MyWidgetProvider extends AppWidgetProvider {

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        Log.d("MyWidgetProvider", "appWidgetIds.lenght:" + appWidgetIds.length);
        for (int appWidgetId : appWidgetIds) {
            Intent svcIntent = new Intent(context, MyWidgetService.class);
            svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

            RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.widget_root);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                widget.setRemoteAdapter(R.id.listView_widget, svcIntent);
            else
                widget.setRemoteAdapter(appWidgetId, R.id.listView_widget, svcIntent);

            /*
            Intent clickIntent = new Intent(context, MainActivity.class);
            PendingIntent clickPI = PendingIntent.getActivity(context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            widget.setPendingIntentTemplate(R.id.listView_widget, clickPI);*/

            appWidgetManager.updateAppWidget(appWidgetId, widget);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}

RemoteViewsService

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class MyWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return (new MyWidgetViewsFactory(this.getApplicationContext(), intent));
    }
}

所有其他资源 can you find in the repo at GitHub.


Logcat 输出:

08-08 02:11:10.858  32427-32444/? D/MyWidgetViewsFactory﹕ getViewAt(0):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@3e7179c9
08-08 02:11:10.860  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:0|1|2
08-08 02:11:10.864  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:3|4|5
08-08 02:11:10.866  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:6|7|8
08-08 02:11:10.927  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(0):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@3e7179c9
08-08 02:11:10.927  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:0|1|2
08-08 02:11:10.927  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:3|4|5
08-08 02:11:10.927  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:6|7|8
08-08 02:11:10.931  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(1):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@23e248ce
08-08 02:11:10.931  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:9|10|11
08-08 02:11:10.931  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:12|13|14
08-08 02:11:10.931  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:15|16|17
08-08 02:11:10.933  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(2):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@16dbf3ef
08-08 02:11:10.933  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:18|19|20
08-08 02:11:10.933  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:21|22|23
08-08 02:11:10.933  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:24|25|26
08-08 02:11:10.936  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(3):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@19d3defc
08-08 02:11:10.936  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:27|28|29
08-08 02:11:10.936  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:30|31|32
08-08 02:11:10.936  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:33|34|35
08-08 02:11:10.938  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(4):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@ee985
08-08 02:11:10.938  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:36|37|38
08-08 02:11:10.938  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:39|40|41
08-08 02:11:10.938  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:42|43|44
08-08 02:11:10.941  32427-32443/? D/MyWidgetViewsFactory﹕ getViewAt(8):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@335e23da
08-08 02:11:10.941  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:72|73|74
08-08 02:11:10.941  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:75|76|77
08-08 02:11:10.941  32427-32443/? D/MyWidgetViewsFactory﹕ subitem:78|79|80
08-08 02:11:10.943  32427-32447/? D/MyWidgetViewsFactory﹕ getViewAt(9):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@229de00b
08-08 02:11:10.943  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:81|82|83
08-08 02:11:10.943  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:84|85|86
08-08 02:11:10.943  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:87|88|89
08-08 02:11:10.945  32427-32444/? D/MyWidgetViewsFactory﹕ getViewAt(5):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@2afdeee8
08-08 02:11:10.945  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:45|46|47
08-08 02:11:10.945  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:48|49|50
08-08 02:11:10.945  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:51|52|53
08-08 02:11:10.948  32427-32444/? D/MyWidgetViewsFactory﹕ getViewAt(7):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@1c599901
08-08 02:11:10.948  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:63|64|65
08-08 02:11:10.948  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:66|67|68
08-08 02:11:10.948  32427-32444/? D/MyWidgetViewsFactory﹕ subitem:69|70|71
08-08 02:11:10.951  32427-32447/? D/MyWidgetViewsFactory﹕ getViewAt(6):de.mayerhofersimon.listviewproblem.MyWidgetViewsFactory$Item@368aa3a6
08-08 02:11:10.951  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:54|55|56
08-08 02:11:10.951  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:57|58|59
08-08 02:11:10.951  32427-32447/? D/MyWidgetViewsFactory﹕ subitem:60|61|62

因此数据正确传递。它只是没有得到正确显示...

顺便说一句:它应该是这样的:http://c.maysi.de/cB8K

会不会是嵌套远程视图的问题? 因为所有外部远程视图都显示正确...

您的问题似乎出在:

if(stundenContainer[j]!=null)
    Log.d("VplanWidgetViewsFactory", "stundenContainer["+j+"]:" + stundenContainer[j].toString());
else
    Log.d("VplanWidgetViewsFactory", "stundenContainer[" + j + "]:null");

if (stundenContainer[j] == null) {
    //Freistunde
    Log.d("VplanWidgetViewsFactory", "Freistunde");
    // HERE -----
    stunden.add(new RemoteViews(context.getPackageName(), R.layout.fragment_stunde_widget));
    faecher.add(new RemoteViews(context.getPackageName(), R.layout.fragment_fach));
    stunden.get(stunden.size() - 1).setTextViewText(R.id.textView_lesson_nr, "" + (j + 1) + ".");
 } else if (!stundenContainer[j].get(0).getSubject().equals("ignore")) {
     Log.d("VplanWidgetViewsFactory", "stundenContainer[j].get(0).getSubject(): " + stundenContainer[j].get(0).getSubject());
     // HERE -----
     stunden.add(new RemoteViews(context.getPackageName(), R.layout.fragment_stunde_widget));

您添加了两次..,但仅在第一项未被忽略时才添加,因此它看起来是随机的。

stunden.add(new RemoteViews(context.getPackageName(), R.layout.fragment_stunde_widget));

您对 ListView 回收问题的看法是正确的。您需要了解事物在地面上的工作原理。

MVC 模式

The Android graphics works on the MVC pattern i.e. Model-View and Controller pattern. Model is your data, database in your case, View is your layout or graphical portion,such as ListView or RecyclerView or RemoteView. Controller changes your view after data update, parent View or ViewGroup in your case the RemoteViewsService.RemoteViewsFactory is the controller. I suggest to read further by googling the MVC model.

Pattern是如何实现的?

只要数据发生变化,控制器就应该更新视图。 Android 框架让您有机会通过覆盖 getViewAt(int position) 在给定位置使用数据显示视图。控制器调用 getViewAt(int position) 以获取 ListView 中给定位置的视图或回收视图。 ListView 或 RecyclerView 仅呈现屏幕上可见的行。例如,如果您在 ListView 中有 100 个项目并且只有 7 个在屏幕上可见,那么它将调用 getItemAt(int) 7 次。每次滚动时,都会为可见行调用 getItemAt(int)。 ListView 和 RemoteView 随意 recycle/reuse 先前传递的 getItemAt(int position) 返回的视图。它确保应用程序的图形部分消耗的内存是有限的

为什么会有奇怪的行为?

首先屏幕上所有可见的东西都是一个View,比如TextView、ImageView、ListView等,否则就无法显示在屏幕上。 RemoteView 不是视图。您传递要使用 RemoteView(视图 + 数据)显示的布局和数据。

这里我指的是你的 Screencast 的解释。

1) Initialization: The ListView in your case, initially creates say 6 rows based on the visible space on screen and getViewAt(int position) is called once if getCount() returns 1. I request to check the return value of the getCount() of the List adapter.
2) You scrolled down: Nothing happens to the ListView and rendering of new Rows.
3) You Scrolled Up: getPositionAt(int position) is called again and RemoteView is passed back.Two rows are visible now. I request to check the getCount() return value. It should be 2 if not than the reason could be caching of rows by ListView.
4) You scrolled down: Nothing happens to the ListView and rendering of new Rows.
5) You Scrolled Up: Refer 3. The getCount() should be 3 and so on.

你应该做什么?

根据您的实施,您只创建了一次 RemoteView 并尝试在 getItemAt(int) 重新使用相同的视图,可能是为了节省布局 inflation 时间。
要解决此问题,您 必须 每次调用 getItemAt(int) 时都提供 FRESH RemoteView

@Override
public RemoteViews getViewAt(int position) 
{
    Log.d("VplanWidgetViewsFactory", "getViewAt("+position+"):"+stunden.get(position));
    //TODO: Store context when constructor is called.
    RemoteView rv = new RemoteViews(context.getPackageName(), R.layout.fragment_stunde_widget);
    rv.setTextViewText(R.id.textView_lesson_nr, "" + (position + 1) + "."); <=  I have not tested this.

    return rv;
    //return stunden.get(position); <=COMMENT THIS
}

我自己找到了答案。

要解决在滚动和调整大小时奇怪地添加视图的问题,您必须在添加子视图的布局上调用 removeAllViews

@Override
    public RemoteViews getViewAt(int position) {
        ...
        RemoteViews itemView = new RemoteViews(context.getPackageName(), R.layout.widget_listview_item);
        itemView.removeAllViews(R.id.linearLayout_item_body);
        ...
        return itemView;
    }

而视图不显示的问题是因为颜色: 添加后

    subitem.setTextColor(R.id.textView_1, context.getResources().getColor(R.color.abc_primary_text_material_light));
    subitem.setTextColor(R.id.textView_2, context.getResources().getColor(R.color.abc_primary_text_material_light));
    subitem.setTextColor(R.id.textView_3, context.getResources().getColor(R.color.abc_primary_text_material_light));

显示所有视图: