4

Im trying to scroll a ListView to a particular position in an AppWidget.

However it does not do anything, i also tried the setPosition method but not working.

Also no errors or stack trace.

Code:

                    if (list.size() == 0) {
                        loadLayout(R.layout.rooster_widget_header);
                        views.addView(R.id.header_container,
                                views);
                    } else {
                        views.setRemoteAdapter(R.id.lvWidget, svcIntent);
                        views.setScrollPosition(R.id.lvWidget, 3);
                    }
Neha Shukla
  • 3,572
  • 5
  • 38
  • 69

4 Answers4

7

Issue: ListView does not have any children until it is displayed. Hence calling setScrollPosition right after setting adpater has no effect. Following is the code in AbsListView which does this check:

final int childCount = getChildCount();
if (childCount == 0) {
    // Can't scroll without children.
    return;
}

Solution: Ideally I would have used ViewTreeObserver.OnGlobalLayoutListener for setting the ListView scroll position, but it is not possible in case of remote views. Set the scroll position and invoke partiallyUpdateAppWidget in a runnable with some delay. I've modified the Android weather widget code and shared in git hub.

public class MyWidgetProvider extends AppWidgetProvider {

    private static HandlerThread sWorkerThread;
    private static Handler sWorkerQueue;

    public MyWidgetProvider() {
        // Start the worker thread
        sWorkerThread = new HandlerThread("MyWidgetProvider-worker");
        sWorkerThread.start();
        sWorkerQueue = new Handler(sWorkerThread.getLooper());
    }

    public void onUpdate(Context context, final AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int i = 0; i < appWidgetIds.length; ++i) {
            ...
            final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            views.setRemoteAdapter(R.id.lvWidget, svcIntent);

            sWorkerQueue.postDelayed(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    views.setScrollPosition(R.id.list, 3);
                    appWidgetManager.partiallyUpdateAppWidget(appWidgetIds[i], views);
                }

            }, 1000);

            appWidgetManager.updateAppWidget(appWidgetIds[i], views);
            ...
        }
    }
}

Here is the screen record. It scrolls to 5th position.

Auto scrolling of ListView in app widget

Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60
  • Actually this works but it does not scroll to the right position, i guess i can fix that. Thanks, shut up and take my rep! –  Sep 11 '14 at 06:46
  • @BenjaminFaal Isn't this verbatim what my answer said? – Vikram Sep 13 '14 at 19:40
  • No not exactly, because of the postDelayed function. –  Sep 14 '14 at 20:02
0

I think i have the solution, i use it in my own code. For information, i have some pages & want to show them on demand :

getListView().setSelection(((moreServices-1)*20)+1);

For you, it will be :

yourListView.setSelection(3);

Hope it helps...

EDIT

I think you should see that... that's older, but maybe that's never changed

How to make a scrollable app widget?

Community
  • 1
  • 1
Sidd
  • 172
  • 9
  • I wish it was, its RemoteViews im using so that is not the way it works. –  Sep 02 '14 at 08:33
  • Have u tried `setRelativeScrollPosition(int viewId, int offset)` ? – Sidd Sep 02 '14 at 08:37
  • And `smoothScrollToPosition (int position, int boundPosition)`? – Sidd Sep 02 '14 at 08:39
  • Its not impossible because those methods are not for nothing in the RemoteViews class. I just think it does not update or sync correctly. –  Sep 02 '14 at 08:48
  • Have you tried to "notify datas changed" before scrolling? – Sidd Sep 02 '14 at 08:49
0

Whit the information I have, I think that the widget doesn't invalidate itself so it doesn't so the changes on it. So I would suggest to refresh your widget. There are two ways to do that, the first one is:

appWidgetManager.updateAppWidget(widgetId, remoteViews);

and there is a second way only in case we don't have direct access to AppWidgetManager

/**
 * Refresh the views on the widget
 * NOTE: Use only in case we dont have direct acces to AppWidgetManager
 */
private void invalidateWidget() {
    ComponentName widget = new ComponentName(this, YourWidgetProvider.class);
    AppWidgetManager manager = AppWidgetManager.getInstance(this);
    manager.updateAppWidget(widget, mRv);
}

Like that the changes should be reflected in the widget. If this doesn't work I am gonna need more details to figure out the problem and edit this answer. Hope it helps.

jpardogo
  • 5,636
  • 2
  • 23
  • 27
0

Two things:

views.setScrollPosition(R.id.lvWidget, 3);

is the correct call. Internally, setScrollPosition(int, int) calls RemoteViews#setInt(viewId, "smoothScrollToPosition", position);.

viewId                   :    your ListView's id
"smoothScrollToPosition" :    method name to invoke on the ListView
position                 :    position to scroll to

So, you are calling the correct method.

Following is a comment for the method AppWidgetManager#partiallyUpdateAppWidget(int, RemoteViews) - taken from the source code of AppWidgetManager.java. I believe it answers your question: ... Use with {RemoteViews#showNext(int)}, {RemoteViews#showPrevious(int)},{RemoteViews#setScrollPosition(int, int)} and similar commands...

/**
 * Perform an incremental update or command on the widget specified by appWidgetId.
 *
 * This update  differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews
 * object which is passed is understood to be an incomplete representation of the widget, and
 * hence is not cached by the AppWidgetService. Note that because these updates are not cached,
 * any state that they modify that is not restored by restoreInstanceState will not persist in
 * the case that the widgets are restored using the cached version in AppWidgetService.
 *
 * Use with {RemoteViews#showNext(int)}, {RemoteViews#showPrevious(int)},
 * {RemoteViews#setScrollPosition(int, int)} and similar commands.
 *
 * <p>
 * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
 * and outside of the handler.
 * This method will only work when called from the uid that owns the AppWidget provider.
 *
 * <p>
 * This method will be ignored if a widget has not received a full update via
 * {@link #updateAppWidget(int[], RemoteViews)}.
 *
 * @param appWidgetId      The AppWidget instance for which to set the RemoteViews.
 * @param views            The RemoteViews object containing the incremental update / command.
 */
public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) {
    partiallyUpdateAppWidget(new int[] { appWidgetId }, views);
}

As the comment indicates, call partiallyUpdateAppWidget(appWidgetId, views) after views.setScrollPosition(R.id.lvWidget, 3).

The comment also warns you: This method will be ignored if a widget has not received a full update via {#updateAppWidget(int[], RemoteViews)}. This might mean that the following calls:

views.setRemoteAdapter(R.id.lvWidget, svcIntent);
views.setScrollPosition(R.id.lvWidget, 3);

should not be made in one update. I suggest that you break these calls into two separate updates:

First:

views.setRemoteAdapter(R.id.lvWidget, svcIntent);
mAppWidgetManager.updateAppWidget(appWidgetIds, views);

Second:

views.setScrollPosition(R.id.lvWidget, 3);
mAppWidgetManager.partiallyUpdateAppWidget(appWidgetId, views);

Note that the first one is a full update, while the second one is only partial.

Vikram
  • 51,313
  • 11
  • 93
  • 122
  • 2
    @BenjaminFaal `No not exactly, because of the postDelayed function.` Sorry about the late reply. But that's exactly what I meant when I said `I suggest that you break these calls into two separate updates`. I thought that part was clear. Anyways, good luck. – Vikram Oct 03 '14 at 06:27