1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.util.AttributeSet;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.FrameLayout;
26 
27 import androidx.core.content.res.TypedArrayUtils;
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceViewHolder;
30 
31 /**
32  * A preference can be simply customized a view by adding layout attribute in xml.
33  * User also can decide whether or not LayoutPreference allows above divider or below divider.
34  *
35  * For instances,
36  *
37  * <com.android.settingslib.widget.LayoutPreference
38  *      ...
39  *      android:layout="@layout/settings_entity_header"
40  *      xxxxxxx:allowDividerAbove="true"
41  *      xxxxxxx:allowDividerBelow="true"
42  *
43  */
44 public class LayoutPreference extends Preference {
45 
46     private final View.OnClickListener mClickListener = v -> performClick(v);
47     private boolean mAllowDividerAbove;
48     private boolean mAllowDividerBelow;
49     private View mRootView;
50 
51     /**
52      * Constructs a new LayoutPreference with the given context's theme and the supplied
53      * attribute set.
54      *
55      * @param context The Context the view is running in, through which it can
56      *                access the current theme, resources, etc.
57      * @param attrs The attributes of the XML tag that is inflating the view.
58      */
LayoutPreference(Context context, AttributeSet attrs)59     public LayoutPreference(Context context, AttributeSet attrs) {
60         super(context, attrs);
61         init(context, attrs, 0 /* defStyleAttr */);
62     }
63 
64     /**
65      * Constructs a new LayoutPreference with the given context's theme, the supplied
66      * attribute set, and default style attribute.
67      *
68      * @param context The Context the view is running in, through which it can
69      *                access the current theme, resources, etc.
70      * @param attrs The attributes of the XML tag that is inflating the view.
71      * @param defStyleAttr An attribute in the current theme that contains a
72      *                     reference to a style resource that supplies default
73      *                     values for the view. Can be 0 to not look for
74      *                     defaults.
75      */
LayoutPreference(Context context, AttributeSet attrs, int defStyleAttr)76     public LayoutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
77         super(context, attrs, defStyleAttr);
78         init(context, attrs, defStyleAttr);
79     }
80 
81     /**
82      * Constructs a new LayoutPreference with the given context's theme and a customized view id.
83      *
84      * @param context The Context the view is running in, through which it can
85      *                access the current theme, resources, etc.
86      * @param resource The view id which you expected to be inflated and show in preference.
87      */
LayoutPreference(Context context, int resource)88     public LayoutPreference(Context context, int resource) {
89         this(context, LayoutInflater.from(context).inflate(resource, null, false));
90     }
91 
92     /**
93      * Constructs a new LayoutPreference with the given context's theme and a customized view.
94      *
95      * @param context The Context the view is running in, through which it can
96      *                access the current theme, resources, etc.
97      * @param view The view which you expected show in preference.
98      */
LayoutPreference(Context context, View view)99     public LayoutPreference(Context context, View view) {
100         super(context);
101         setView(view);
102     }
103 
init(Context context, AttributeSet attrs, int defStyleAttr)104     private void init(Context context, AttributeSet attrs, int defStyleAttr) {
105         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Preference);
106         mAllowDividerAbove = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerAbove,
107                 R.styleable.Preference_allowDividerAbove, false);
108         mAllowDividerBelow = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerBelow,
109                 R.styleable.Preference_allowDividerBelow, false);
110         a.recycle();
111 
112         a = context.obtainStyledAttributes(
113                 attrs, R.styleable.Preference, defStyleAttr, 0);
114         int layoutResource = a.getResourceId(R.styleable.Preference_android_layout, 0);
115         if (layoutResource == 0) {
116             throw new IllegalArgumentException("LayoutPreference requires a layout to be defined");
117         }
118         a.recycle();
119 
120         // Need to create view now so that findViewById can be called immediately.
121         final View view = LayoutInflater.from(getContext())
122                 .inflate(layoutResource, null, false);
123         setView(view);
124     }
125 
setView(View view)126     private void setView(View view) {
127         setLayoutResource(R.layout.layout_preference_frame);
128         mRootView = view;
129         setShouldDisableView(false);
130     }
131 
132     @Override
onBindViewHolder(PreferenceViewHolder holder)133     public void onBindViewHolder(PreferenceViewHolder holder) {
134         holder.itemView.setOnClickListener(mClickListener);
135 
136         final boolean selectable = isSelectable();
137         holder.itemView.setFocusable(selectable);
138         holder.itemView.setClickable(selectable);
139         holder.setDividerAllowedAbove(mAllowDividerAbove);
140         holder.setDividerAllowedBelow(mAllowDividerBelow);
141 
142         FrameLayout layout = (FrameLayout) holder.itemView;
143         layout.removeAllViews();
144         ViewGroup parent = (ViewGroup) mRootView.getParent();
145         if (parent != null) {
146             parent.removeView(mRootView);
147         }
148         layout.addView(mRootView);
149     }
150 
151     /**
152      * Finds the view with the given ID.
153      *
154      * @param id the ID to search for
155      * @return a view with given ID if found, or {@code null} otherwise
156      */
findViewById(int id)157     public <T extends View> T findViewById(int id) {
158         return mRootView.findViewById(id);
159     }
160 
161     /**
162      * LayoutPreference whether or not allows to set a below divider.
163      */
setAllowDividerBelow(boolean allowed)164     public void setAllowDividerBelow(boolean allowed) {
165         mAllowDividerBelow = allowed;
166     }
167 
168     /**
169      * Return a value whether or not LayoutPreference allows to set a below divider.
170      */
isAllowDividerBelow()171     public boolean isAllowDividerBelow() {
172         return mAllowDividerBelow;
173     }
174 }
175