How to hide partially visible views in Android xml layout without code?

Please read the question carefully before answering!

Suppose I have two kinds:

  • first (yellow) below
  • second (cyan) filling the rest of the view above first

The size of the parent view is dynamic.

How can I achieve the expected column context below when the parent height is dynamically set to the values ​​in the first column? Notice how the partially visible view is simply hidden rather than cropped.

What I want to achieve

Limitations

  • Any code is not an option , I know that you can solve the problem with: second.setVisibility(second.getHeight() < SECONDS_PREFERRED_SIZE? GONE : VISIBLE) but I do not have access to getHeight() , so the solution should be open xml.
    It can be relaxed to encode only the user interface state record via RemoteViews .
  • Based on the above , user views are not allowed , there are n’t even libraries , only the basic layout managers :
    FrameLayout , LinearLayout , RelativeLayout , GridLayout
    or as a last resort, one of the advanced:
    ViewFlipper , ListView , GridView , StackView or AdapterViewFlipper
  • layout_width and layout_height dynamic
    In this example, I fixed it so that it would be easy to try various options. The above image is a layout_height , changed to a display value, simulating the features I want to handle.
  • I used TextView , but it should generally be applicable to any View
  • Observers may have noticed that the above limits the solution to Home screens application widgets with RemoteViews .

Is trying

I tried implementing with RelativeLayout and LinearLayout , but they both behave like the Actual column in the picture.

RelativeLayout

 <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

 <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> 
+5
source share
2 answers

If it's RemoteViews / widget, try using multiple layout files. Extend AppWidgetProvider and override the onAppWidgetOptionsChanged method. In this function, you will want to do the following:

 Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); 

Then a case at minHeight to determine which layout is most suitable for inflating. This page is incredibly useful when creating widgets.

+1
source

While OPTION_APPWIDGET_MIN/MAX_WIDTH/HEIGHT does not really report real values, I have found a way around it. Note that this does not really solve the problem, but is a rather confusing workaround.

Source of idea

 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]) 

Study

When RemoteViews.setOnClickPendingIntent broadcasts the faucet by the user, it will place the view boundaries in the Intent . As you can see in bnds=[281,756][533,1071] , the views 533-281=252 wide and 1071-756=315 tall are pixels, and Galaxy S4 is a XXHDPI (3x) device, so 252x315 / 3 = 84x105 , which was my empirical observation about the size of widget cells.

The specified boundaries depend on the viewId specified by setOnClickPendingIntent , so to get the size of the cell, you need to attach the pending intent to the root view in the layout. My widget has a tap-to-refresh listener that I can safely attach to the root layout. There are other views that have PendingIntent s, but they span the root layout, so their intent will be translated as normal onClickListener s.

Decision

Now this means that we know the exact size of the cell, based on this it will be possible to calculate the size of individual views (for simpler layouts). Essentially, we have a way to get root.getHeight() , we can "hard code" the size of first in R.dimen.firstHeight , and from this we can calculate whether to show first , second or not.

Or use these borders just like @AndrewOrobator suggested: inflate the layout to fit the size.

User interaction required

The only problem that requires user interaction at least once. When the widget host sends an update, there are no restrictions, so the user must click on the view so that we have the size. I would wrap this around, forcing the user to click on the view once (immediately after installing the widget on the house):

 @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); } 
0
source

Source: https://habr.com/ru/post/1206423/


All Articles