带有ListView的小部件显示每个找到的项目都是“正在加载”

11
我正在尝试为我的Android应用程序构建小部件,但我遇到了问题,尽管ListView通过调试器可以看到getViewAt方法在我的RemoteViewFactory实现中已被调用并返回适当的RemoteViews,但找到的每个条目仅显示为“正在加载...”。

Android emulator screenshot showing widget with four entries, all four showing 'loading'

记录一下:我的数据库中确实有4个条目,并且getViewAt方法被调用了四次,每次都返回一个适当的列表项。
我基于Android文档编写了代码,并使用StackView示例作为技术参考。
在尝试修复这个问题时,我查阅了stackoverflow并发现了一些过去的问题。修复的最常见建议是:
  • 确保getViewTypeCount方法返回一个非0值(通常为1,因为大多数小部件只会使用一种视图类型);
  • 确保仅使用支持的视图
  • 还有一些模糊的建议,如应该或不应该使用某些任意视图属性。
我尝试了以上所有建议,并且只使用支持的视图,但是我的项目仍然没有显示。这里有人有什么想法吗?
我编写的相关代码列表:
AndroidManifest.xml文件中添加的条目:
    <service
        android:name=".widget.DfdWidgetService"
        android:permission="android.permission.BIND_REMOTEVIEWS" />

    <receiver android:name=".widget.DfdWidget">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>

        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/dfd_widget_info" />
    </receiver>

小部件布局xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80DDE8ED"
android:padding="@dimen/widget_margin">

<ListView
    android:id="@+id/widgetRemindersListView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:listitem="@layout/dfd_widget_list_item">

</ListView>

<TextView
    android:id="@+id/widgetEmptyViewText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="You have no reminders for today, nice!"
    android:textAlignment="center"
    android:textSize="20sp" />

</RelativeLayout>

小部件列表项 XML

(作为测试,我尝试仅删除 TextViews,以查看 Android 是否认为 ImageView 有问题。但这并不重要。)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widgetListItem"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
    android:id="@+id/widgetCompletedCheckBox"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_alignParentStart="true"
    android:layout_marginStart="10dp"
    android:layout_marginTop="15dp"
    android:layout_marginEnd="10dp"
    android:layout_marginBottom="10dp"
    app:srcCompat="@android:drawable/checkbox_off_background" />

<TextView
    android:id="@+id/widgetReminderName"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:layout_toEndOf="@id/widgetCompletedCheckBox"
    android:text="Reminder name"
    android:textColor="#000000"
    android:textSize="24sp" />

<TextView
    android:id="@+id/widgetReminderDate"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/widgetReminderName"
    android:layout_toEndOf="@id/widgetCompletedCheckBox"
    android:text="Ma 1 jan"
    android:textColor="#2196F3" />

<ImageView
    android:id="@+id/widgetReminderRepeating"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_below="@id/widgetReminderName"
    android:layout_toEndOf="@id/widgetReminderDate"
    android:background="#00FFFFFF"
    app:srcCompat="@android:drawable/ic_menu_revert"
    tools:visibility="invisible" />

<ImageView
    android:id="@+id/widgetIsImportant"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_alignParentEnd="true"
    app:srcCompat="@android:drawable/btn_star_big_off" />
</RelativeLayout>

小部件提供程序类
public class DfdWidget extends AppWidgetProvider {

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        Intent intent = new Intent(context, DfdWidgetService.class);

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.dfd_widget);
        remoteViews.setRemoteAdapter(R.id.widgetRemindersListView, intent);
        remoteViews.setEmptyView(R.id.widgetRemindersListView, R.id.widgetEmptyViewText);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }

    @Override
    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
        super.onRestored(context, oldWidgetIds, newWidgetIds);
    }
}

小部件服务类
public class DfdWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new DfdRemoteViewsFactory(getApplicationContext());
    }

    class DfdRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
        private final Context context;
        private ReminderRepository reminderRepository;
        private List<Reminder> reminders;

        public DfdRemoteViewsFactory(Context context) {
            this.context = context;
            this.reminderRepository = ReminderRepository.getReminderRepository(context);
        }

        @Override
        public void onCreate() {
            reminders = reminderRepository.getAllRemindersForTodayList();
        }

        @Override
        public void onDataSetChanged() {
            reminders = reminderRepository.getAllRemindersForTodayList();
        }

        @Override
        public void onDestroy() {
            reminders.clear();
        }

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

        @Override
        public RemoteViews getViewAt(int position) {
            Reminder reminder = reminders.get(position);
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem);

            remoteView.setTextViewText(R.id.widgetReminderName, reminder.getName());
            // Removed code that sets the other fields as I tried it with and without and it didn't matter. So removed for brevity

            return remoteView;
        }

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

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

        @Override
        public long getItemId(int position) {
            return reminders.get(position).getId();
        }

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

1
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem); - 在 getViewAt() 中,应该是一个 R.layout,而不是一个 R.id。可能只是一个笔误,假设您将布局命名为相同的名称。此外,虽然与当前问题无关,但在您的布局中使用 app 命名空间前缀的任何内容都不会在 RemoteViews 中起作用;例如,app:srcCompat。只是提供信息。 - Mike M.
1
实际上,你在这个评论中说得非常到位!你和CommonsWare都非常快地找到了这个问题! - Lynxian
@Lynxian 我想知道如何将数据从Widget Provider类发送到Widget Service类? 设置reminderRepository值的函数在哪里? - MmD
1个回答

5
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.id.widgetListItem)

你试图使用的RemoteViews 构造函数需要将布局资源ID作为第二个参数。而你却传递了一个小部件ID。
所以,请使用 R.layout.whateverYourLayoutNameIsForTheListItems 替换 R.id.widgetListItem,其中 whateverYourLayoutNameIsForTheListItems 是你列表项布局的名称。

我不知道你和Mike M是如何这么快找到它的,但干杯! - Lynxian
2
@Lynxian:事实上,适当的计数显示出来表明,正如你所指出的那样,你的RemoteViewsService被调用了。快速检查行布局XML表明,“Loading…”文本不是来自你的代码,这意味着小部件主机在使用你的行时遇到了困难。这将问题限制在设置行的RemoteViews的位置上,而这只有几行代码。 - CommonsWare

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