Search code examples
androidandroid-layoutlistviewandroid-dialogfragment

ListView with LinearLayouts in DialogFragment doesn't wrap content


I've made a construction in Android to easy build & show a DialogFragment on the screen. The dialog contains a TextView, ListView and a Button. It's build to easily inflate different views in the ListView so I can make custom Dialogs.

Example: enter image description here

The left image is correct. On the right I inflated an extra view. A view I use to display text in the DialogFragment. But when I add that view it doesn't wrap content anymore. The ListView makes itself scrollable.

I can add more of the bluebuttons you see on the image and the ListView will still wrap_content correct. But when I add one or more of the other views it the ListView makes itself scrollable.

The problem is I just can't get the ListView to wrap_content correct.

This is the xml of the "blue button" view: (the correct inflated view)

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/overlay_item_label"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_weight="1"
     android:orientation="vertical"
     android:weightSum="1">

     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@drawable/tile_button_categorytile"
         android:orientation="horizontal">

         <ImageView
             android:id="@+id/popUpImage"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_margin="6dp"
             android:scaleType="fitCenter"
             android:src="@drawable/ic_speaker" />

        <com.joanzapata.iconify.widget.IconTextView
             android:id="@+id/popUpButton"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center"
             android:layout_margin="4dp"
             android:layout_marginRight="15dp"
             android:layout_weight="1"
             android:padding="4dp"
             android:text="Bewerken {fa-android}"
             android:textColor="@color/white"
             android:textSize="14sp"
             android:textStyle="bold" />
    </LinearLayout>
</LinearLayout>

The view used as text in the DialogFragment: (which causes the ListView to make itself scrollable)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/overlay_item_label"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"

    android:background="@color/white"
    android:layout_weight="1"
    android:weightSum="1">

        <TextView
            android:id="@+id/LabelOverlay"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:gravity="center"
            android:paddingLeft="5dp"
            android:paddingRight="5dp"
            android:text="@string/placeholder"
            android:textColor="@color/purple"
            android:textSize="12sp" />
</LinearLayout>

When I add extra paddingTop and paddingBottom to the view that is used to show text, the ListView doesn't make itself scrollable (on only a few devices). The padding is set to around 20dp and then it on some devices it doesn't make itself scrollable, but then on other devices its too much padding so it shows whitespace...

This is the code of the fragment that shows the DialogFragment:

public class PopUpFragmentPhone extends DialogFragment {
    PopUp popUpData;
    ListView listView;

    public static PopUpFragmentPhone newInstance(PopUp data) {
        PopUpFragmentPhone popUpFragment = new PopUpFragmentPhone();

        Bundle args = new Bundle();
        args.putParcelable("PopUpData", data);
        popUpFragment.setArguments(args);
        return popUpFragment;
    }

    @Override
    public void onStart(){
        super.onStart();
        Window window = getDialog().getWindow();
        WindowManager.LayoutParams windowParams = window.getAttributes();
        windowParams.dimAmount = 0.70f;

        windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        window.setAttributes(windowParams);

        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

        getDialog().getWindow().setLayout(MainApplication.width / 2, ViewGroup.LayoutParams.WRAP_CONTENT);
        getDialog().setCanceledOnTouchOutside(true);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme);
        popUpData = getArguments().getParcelable("PopUpData"); //get the PopUp class.
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.pop_up_phone, container, false);

        //Set the title of the popUp
        TextView popUpTitle = (TextView)v.findViewById(R.id.overlayTitle);
        popUpTitle.setText(popUpData.getHeader());

        listView = (ListView) v.findViewById(R.id.listViewItemsOverlay);
        listView.setAdapter(new PopUpAdapterPhone(MainApplication.getContext(), 1, popUpData.getItems(), this));

        boolean showCancel = popUpData.isCancel();
        Button cancelButton = (Button) v.findViewById(R.id.cancel_button);
        Drawable cancelIcon = new IconDrawable(getActivity(), FontAwesomeIcons.fa_times).colorRes(R.color.red).sizeDp(20);
        cancelButton.setCompoundDrawablesWithIntrinsicBounds(cancelIcon, null, null, null);
        if(showCancel) {
            // Cancel button click
            cancelButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    getDialog().dismiss();
                }
            });
        } else {
            cancelButton.setVisibility(View.GONE);
        }

        getDialog().getWindow().setLayout(MainApplication.width / 2, ViewGroup.LayoutParams.WRAP_CONTENT);//set the size of the fragment, so the rest is a touch to cancel the popUP

        return v;
    }
}

The view of the pop up fragment:

<?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      android:gravity="center"
      android:padding="5dp"
      android:weightSum="3"
      android:background="@drawable/tile_button_wordtile"
      android:id="@+id/overlay_master_view">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/menu"
            android:id="@+id/overlayTitle"
            android:textStyle="bold"
            android:textSize="15sp"
            android:padding="4dp"
            android:textColor="@color/purple"
            android:layout_weight="1"
            android:background="@color/white"
            android:layout_margin="4dp"
            android:layout_gravity="center_horizontal" />

       <ListView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="none"
            android:layout_weight="1"
            android:divider="@android:color/transparent"
            android:dividerHeight="5.0sp"
            android:id="@+id/listViewItemsOverlay">

        </ListView>

        <com.joanzapata.iconify.widget.IconButton
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="start"
            android:padding="5dp"
            android:layout_weight="1"
            android:textStyle="bold"
            android:textSize="15sp"
            android:drawablePadding="5dp"
            android:id="@+id/cancel_button"
            android:layout_marginTop="5dp"
            android:textColor="@color/red"
            android:background="@drawable/tile_button_functiontile"
            android:text="@string/cancel"/>
 </LinearLayout>

I tried lots of things, inflate it in a LinearLayout or RelativeLayout. Wrap_content, match_parent, weightSum and all that kinds of stuff. Tried changing the layouts in a lot of ways but it all didn't work out.

I hope someone can help me.


Solution

  • Note: In the code below, I've removed the backgrounds as I didn't have them.

    But basically you shouldn't have to change it too much to match what you want.

    Your dialog

    import android.app.Dialog;
    import android.os.Bundle;
    import android.support.v4.app.DialogFragment;
    import android.support.v7.app.AlertDialog;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.ListView;
    
    import java.util.ArrayList;
    
    public class CustomDialogFragment extends DialogFragment {
    
        private static final String DIALOG_DATA = "my.app.package.DIALOG_DATA";
    
        public static CustomDialogFragment newInstance(ArrayList<ListElement> data) {
            CustomDialogFragment f = new CustomDialogFragment();
            Bundle args = new Bundle();
            args.putParcelableArrayList(DIALOG_DATA, data);
            f.setArguments(args);
    
            return f;
        }
    
        @Override
        public void onResume() {
            super.onResume();
            // Calculate the height/width you want
            // See: https://stackoverflow.com/a/12923805/1827254 for more info
            // In this example, the width is not changed as retrieved from the LayoutParams of the dialog.
    
            WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
            int width = params.width; //getResources().getDimensionPixelSize(R.dimen.popup_width);
            int height = getResources().getDimensionPixelSize(R.dimen.popup_height);
    
            getDialog().getWindow().setLayout(width, height);
        }
    
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            ArrayList<ListElement> data = getArguments().getParcelableArrayList(DIALOG_DATA);
    
            // Inflate the dialog layout
            View view = View.inflate(getContext(), R.layout.custom_dialog, null);
    
            ListView listView = view.findViewById(R.id.dialog_list);
            listView.setAdapter(new CustomAdapter(getContext(), data));
    
            View btn = view.findViewById(R.id.dialog_cancel_button);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    dismiss(); // Dismiss the dialog.
                }
            });
    
            return new AlertDialog.Builder(getActivity())
                    .setView(view)
                    .create();
        }
    }
    

    The dialog layout

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/overlay_master_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="5dp">
    
        <!-- Title of the view -->
    
        <!-- Note: instead of using 'android:text="@string/placehoder"' use
             'tools:text="My preview text"'. Otherwise the system has to set your
             placeholder value and then replace it with the actual value you want in the code -->
        <TextView
            android:id="@+id/dialog_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:background="@color/white"
            android:padding="4dp"
            android:text="@string/menu"
            android:textColor="@color/purple"
            android:textSize="15sp"
            android:textStyle="bold"
            tools:text="Preview text" />
    
        <!-- The ListView needs to have a height of 0 in order to let
        the button to be displayed below it-->
        <ListView
            android:id="@+id/dialog_list"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:divider="@android:color/transparent"
            android:dividerHeight="5.0sp"
            android:scrollbars="none" />
    
        <!-- Button 'cancel'-->
        <com.joanzapata.iconify.widget.IconButton
            android:id="@+id/dialog_cancel_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:drawablePadding="5dp"
            android:gravity="start"
            android:padding="5dp"
            android:text="@string/cancel"
            android:textColor="@color/red"
            android:textSize="15sp"
            android:textStyle="bold" />
    </LinearLayout>
    

    The list adapter

     class CustomAdapter extends ArrayAdapter<ListElement> {
        private final LayoutInflater mInflater;
    
        public CustomAdapter(Context context, ArrayList<ListElement> data) {
            super(context, 0, data);
            mInflater = LayoutInflater.from(context);
        }
    
        @NonNull
        @Override
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            // Get the data item for this position
            ListElement element = getItem(position);
            // Check if an existing view is being reused, otherwise inflate the view
            if (convertView == null) {
                // Use your custom layout
                convertView = mInflater.inflate(R.layout.list_element, parent, false);
            }
    
            // Get the views and set the data
    
            TextView tv = (TextView) convertView.findViewById(R.id.elt_text);
            tv.setText(element.getText());
    
            ImageView imageView = (ImageView) convertView.findViewById(R.id.elt_image);
            imageView.setImageResource(element.getIconRes());
    
            return convertView;
        }
    }
    

    List element layout

    I think setting the weight to every component was one of your issues

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/overlay_item_label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/blue"
        android:orientation="horizontal">
    
        <ImageView
            android:id="@+id/elt_image"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="6dp"
            android:scaleType="fitCenter"
            tools:src="@mipmap/ic_launcher" />
    
        <com.joanzapata.iconify.widget.IconTextView
            android:id="@+id/elt_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_margin="4dp"
            android:layout_marginRight="15dp"
            android:padding="4dp"
            android:textColor="@color/white"
            android:textSize="14sp"
            android:textStyle="bold"
            tools:text="Bewerken {fa-android}" />
    </LinearLayout>
    

    An element of the list

    import android.os.Parcel;
    import android.os.Parcelable;
    import android.support.annotation.DrawableRes;
    
    /**
     * Every element of the list has an icon and a text.
     * You can add other parameters
     */
    
    public class ListElement implements Parcelable {
        private String mText;
        @DrawableRes
        private Integer mIconRes;
    
        public ListElement() {
            // Default constructor
        }
    
        public ListElement(String text, @DrawableRes int resId) {
            this.mText = text;
            this.mIconRes = resId;
        }
    
        protected ListElement(Parcel in) {
            mText = in.readString();
            if (in.readByte() == 0) {
                mIconRes = null;
            } else {
                mIconRes = in.readInt();
            }
        }
    
        public static final Creator<ListElement> CREATOR = new Creator<ListElement>() {
            @Override
            public ListElement createFromParcel(Parcel in) {
                return new ListElement(in);
            }
    
            @Override
            public ListElement[] newArray(int size) {
                return new ListElement[size];
            }
        };
    
        public String getText() {
            return mText;
        }
    
        public Integer getIconRes() {
            return mIconRes;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel parcel, int i) {
            parcel.writeString(mText);
            if (mIconRes == null) {
                parcel.writeByte((byte) 0);
            } else {
                parcel.writeByte((byte) 1);
                parcel.writeInt(mIconRes);
            }
        }
    }
    

    Use case example (in MainActivity)

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = MainActivity.class.getSimpleName();
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    
            ArrayList<ListElement> data = generateData();
    
            // Create and show the dialog.
            DialogFragment newFragment = CustomDialogFragment.newInstance(data);
            newFragment.show(ft, "dialog");
        }
    
        private ArrayList<ListElement> generateData() {
            ArrayList<ListElement> list = new ArrayList<>();
    
            for (int i = 0; i < 10; i++) {
                list.add(new ListElement("elt " + i, R.mipmap.ic_launcher));
            }
    
            return list;
        }
    }