Please read the question fully before answering!
Suppose I have two views:
first
(yellow) in the bottomsecond
(cyan) filling the rest of the view above first
The size of the parent view is dynamic.
How can I achieve the below Expected column UI when the parent height is dynamically set to the values in the first column? Notice how the partially visible view is just hidden instead of cropped.
second.setVisibility(second.getHeight() < SECONDS_PREFERRED_SIZE? GONE : VISIBLE)
but I don't have access to getHeight()
so the solution must be bare xml.RemoteViews
.FrameLayout
, LinearLayout
, RelativeLayout
, GridLayout
ViewFlipper
, ListView
, GridView
, StackView
or AdapterViewFlipper
layout_width
and layout_height
is dynamiclayout_height
changed to the displayed value simulating the possibilities I want to handle.TextView
, but it should be generally applicable to any View
RemoteViews
.I tried implementing with RelativeLayout
and LinearLayout
, but they both behave as the Actual column on the picture.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="64dp" (actually fill_parent, but fix it for sake of simplicity)
android:layout_height="fill_parent" (first column on picture)
android:layout_gravity="center"
android:background="#666"
tools:ignore="HardcodedText,RtlHardcoded"
>
<TextView
android:id="@+id/first"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:background="#8ff0"
android:text="First"
/>
<TextView
android:id="@+id/second"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@id/first"
android:background="#80ff"
android:text="Second"
android:gravity="center"
/>
</RelativeLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="64dp" (actually fill_parent, but fix it for sake of simplicity)
android:layout_height="fill_parent" (first column on picture)
android:layout_gravity="center"
android:background="#666"
android:orientation="vertical"
tools:ignore="HardcodedText,RtlHardcoded"
>
<TextView
android:id="@+id/second"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#80ff"
android:text="Second"
android:gravity="center"
/>
<TextView
android:id="@+id/first"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_gravity="right"
android:background="#8ff0"
android:text="First"
/>
</LinearLayout>
If this is a RemoteViews/widget, try using multiple layout files. Extend AppWidgetProvider and override the onAppWidgetOptionsChanged
method. In this function you'll want to do the following:
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
Then case on minHeight to determine which layout is most appropriate to inflate. This page is incredibly useful when making widgets.
While the OPTION_APPWIDGET_MIN/MAX_WIDTH/HEIGHT
doesn't really report the real values, I found a way around it. Note that this doesn't really solve the problem at hand, but is a rather convoluted workaround.
Normal update from the widget host
26061-26061/net.twisterrob.app.debug V/TAG﹕ onReceive(Intent { act=android.appwidget.action.APPWIDGET_UPDATE flg=0x10000010 cmp=net.twisterrob.app.debug/net.twisterrob.app.AppWidgetProvider (has extras) } (Bundle[{appWidgetIds=[I@42b14090}]))
26061-26061/net.twisterrob.app.debug V/TAG﹕ onUpdate([483])
User tap with setOnClickPendingIntent
26061-26061/net.twisterrob.app.debug V/TAG﹕ onReceive(Intent { act=android.appwidget.action.APPWIDGET_UPDATE flg=0x10000010 cmp=net.twisterrob.app.debug/net.twisterrob.app.AppWidgetProvider bnds=[281,756][533,1071] (has extras) } (Bundle[{appWidgetIds=[I@42b14090}]))
26061-26061/net.twisterrob.app.debug V/TAG﹕ onUpdate([483])
When RemoteViews.setOnClickPendingIntent
broadcasts the tap by the user, it'll put the bounds of the view in the Intent
. As you can see in bnds=[281,756][533,1071]
the views is 533-281=252
wide and 1071-756=315
tall, these are pixels and the Galaxy S4 is an XXHDPI (3x) device, so 252x315 / 3 = 84x105
which is exactly what my empirical observation was about the widget cell size.
The bounds given depends on the viewId
given to setOnClickPendingIntent
, so to get the cell size, one will need to attach the pending intent on root view in the layout. My widget has a "tap-to-refresh" listener which I can safely attach to the root layout. There are other views which do have PendingIntent
s, but those cover the root layout so their intent will be broadcast, just like regular onClickListener
s.
Now, this means that we know the exact cell size, based on this it may be possible to calculate some views' size by hand (for simpler layouts). Essentially we have way to get root.getHeight()
, we can "hardcode" the size of first
into a R.dimen.firstHeight
, and from this we can calculate whether to show first
, second
or none.
Or, make use of these bounds just like @AndrewOrobator suggested: inflate a layout appropriate for the size.
The only problem that this requires user interaction at least once. When the widget host sends the update there are no bounds, so the user must tap a view so we have the size. I would work around this, by forcing the user to tap on the view once (just after they installed the widget to home):
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
for (int appWidgetId : getAppWidgetIds(intent)) {
Rect storedBounds = getSizeFromPreferences(appWidgetId);
Rect sourceBounds = intent.getSourceBounds();
if (storedBounds == null && sourceBounds != null) {
// no stored size, but we just received one, save it
setSizeToPreferences(appWidgetId, sourceBounds);
storedBounds = sourceBounds;
}
if (storedBounds == null) {
// we didn't have a stored size, neither received one
// force the user to interact with the widget once so we have a size
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_tap_to_refresh);
views.setOnClickPendingIntent(R.id.root, createRefresh(context, appWidgetId));
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views);
} else {
// update as usual, and use the bounds from getSizeFromPreferences(appWidgetId)
onUpdate(context, AppWidgetManager.getInstance(context), new int[] {appWidgetId});
}
}
} else {
super.onReceive(context, intent);
}
}
private @NonNull int[] getAppWidgetIds(Intent intent) {
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds == null) {
appWidgetIds = new int[0];
}
return appWidgetIds;
}
private @NonNull PendingIntent createRefresh(Context context, int appWidgetId) {
Intent intent = new Intent(context, MyAppWidgetProvider.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetId);
return PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With