1 /*
2  * Copyright (C) 2021 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.collapsingtoolbar;
18 
19 import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
20 
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.content.res.Configuration;
24 import android.graphics.text.LineBreakConfig;
25 import android.os.Build;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.FrameLayout;
31 import android.widget.Toolbar;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.appcompat.app.AppCompatActivity;
36 import androidx.coordinatorlayout.widget.CoordinatorLayout;
37 
38 import com.android.settingslib.widget.R;
39 
40 import com.google.android.material.appbar.AppBarLayout;
41 import com.google.android.material.appbar.CollapsingToolbarLayout;
42 
43 /**
44  * A delegate that allows to use the collapsing toolbar layout in hosts that doesn't want/need to
45  * extend from {@link CollapsingToolbarBaseActivity} or from {@link CollapsingToolbarBaseFragment}.
46  */
47 public class CollapsingToolbarDelegate {
48     private static final String TAG = "CTBdelegate";
49     /** Interface to be implemented by the host of the Collapsing Toolbar. */
50     public interface HostCallback {
51         /**
52          * Called when a Toolbar should be set on the host.
53          *
54          * <p>If the host wants action bar to be modified, it should return it.
55          */
56         @Nullable
setActionBar(Toolbar toolbar)57         ActionBar setActionBar(Toolbar toolbar);
58 
59         /** Sets support tool bar and return support action bar, this is for AppCompatActivity. */
60         @Nullable
setActionBar( androidx.appcompat.widget.Toolbar toolbar)61         default androidx.appcompat.app.ActionBar setActionBar(
62                 androidx.appcompat.widget.Toolbar toolbar) {
63             return null;
64         }
65 
66         /** Sets a title on the host. */
setOuterTitle(CharSequence title)67         void setOuterTitle(CharSequence title);
68     }
69 
70     private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
71 
72     @Nullable
73     private CoordinatorLayout mCoordinatorLayout;
74     @Nullable
75     private CollapsingToolbarLayout mCollapsingToolbarLayout;
76     @Nullable
77     private AppBarLayout mAppBarLayout;
78     @NonNull
79     private Toolbar mToolbar;
80     @NonNull
81     private FrameLayout mContentFrameLayout;
82     @NonNull
83     private final HostCallback mHostCallback;
84 
CollapsingToolbarDelegate(@onNull HostCallback hostCallback)85     public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback) {
86         mHostCallback = hostCallback;
87     }
88 
89     /** Method to call that creates the root view of the collapsing toolbar. */
90     @SuppressWarnings("RestrictTo")
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container)91     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
92         return onCreateView(inflater, container, null);
93     }
94 
95     /** Method to call that creates the root view of the collapsing toolbar. */
96     @SuppressWarnings("RestrictTo")
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, Activity activity)97     View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
98             Activity activity) {
99         final View view =
100                 inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
101         if (view instanceof CoordinatorLayout) {
102             mCoordinatorLayout = (CoordinatorLayout) view;
103         }
104         mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
105         mAppBarLayout = view.findViewById(R.id.app_bar);
106         if (mCollapsingToolbarLayout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
107             mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
108             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
109                 mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST);
110                 mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder ->
111                         builder.setLineBreakConfig(
112                                 new LineBreakConfig.Builder()
113                                         .setLineBreakWordStyle(
114                                                 LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
115                                         .build()));
116             }
117         }
118         autoSetCollapsingToolbarLayoutScrolling();
119         mContentFrameLayout = view.findViewById(R.id.content_frame);
120         if (activity instanceof AppCompatActivity) {
121             Log.d(TAG, "onCreateView: from AppCompatActivity and sub-class.");
122             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
123                 initSupportActionBar(inflater);
124             } else {
125                 initRSupportActionBar(view);
126             }
127         } else {
128             Log.d(TAG, "onCreateView: from NonAppCompatActivity.");
129             mToolbar = view.findViewById(R.id.action_bar);
130             final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
131             // Enable title and home button by default
132             if (actionBar != null) {
133                 actionBar.setDisplayHomeAsUpEnabled(true);
134                 actionBar.setHomeButtonEnabled(true);
135                 actionBar.setDisplayShowTitleEnabled(true);
136             }
137         }
138         return view;
139     }
140 
initSupportActionBar(@onNull LayoutInflater inflater)141     private void initSupportActionBar(@NonNull LayoutInflater inflater) {
142         if (mCollapsingToolbarLayout == null) {
143             return;
144         }
145         mCollapsingToolbarLayout.removeAllViews();
146         inflater.inflate(R.layout.support_toolbar, mCollapsingToolbarLayout);
147         final androidx.appcompat.widget.Toolbar supportToolbar =
148                 mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
149         final androidx.appcompat.app.ActionBar actionBar =
150                 mHostCallback.setActionBar(supportToolbar);
151         if (actionBar != null) {
152             actionBar.setDisplayHomeAsUpEnabled(true);
153             actionBar.setHomeButtonEnabled(true);
154             actionBar.setDisplayShowTitleEnabled(true);
155         }
156     }
157 
initRSupportActionBar(View view)158     private void initRSupportActionBar(View view) {
159         view.findViewById(R.id.action_bar).setVisibility(View.GONE);
160         final androidx.appcompat.widget.Toolbar supportToolbar =
161                 view.findViewById(R.id.support_action_bar);
162         supportToolbar.setVisibility(View.VISIBLE);
163         final androidx.appcompat.app.ActionBar actionBar =
164                 mHostCallback.setActionBar(supportToolbar);
165         if (actionBar != null) {
166             actionBar.setDisplayHomeAsUpEnabled(true);
167             actionBar.setHomeButtonEnabled(true);
168             actionBar.setDisplayShowTitleEnabled(true);
169         }
170     }
171 
172     /** Return an instance of CoordinatorLayout. */
173     @Nullable
getCoordinatorLayout()174     public CoordinatorLayout getCoordinatorLayout() {
175         return mCoordinatorLayout;
176     }
177 
178     /** Sets the title on the collapsing layout, delegating to host if needed. */
setTitle(CharSequence title)179     public void setTitle(CharSequence title) {
180         if (mCollapsingToolbarLayout != null) {
181             mCollapsingToolbarLayout.setTitle(title);
182         } else {
183             mHostCallback.setOuterTitle(title);
184         }
185     }
186 
187     /** Returns an instance of collapsing toolbar. */
188     @Nullable
getCollapsingToolbarLayout()189     public CollapsingToolbarLayout getCollapsingToolbarLayout() {
190         return mCollapsingToolbarLayout;
191     }
192 
193     /** Return the content frame layout. */
194     @NonNull
getContentFrameLayout()195     public FrameLayout getContentFrameLayout() {
196         return mContentFrameLayout;
197     }
198 
getToolbar()199     public Toolbar getToolbar() {
200         return mToolbar;
201     }
202 
203     /** Return an instance of app bar. */
204     @Nullable
getAppBarLayout()205     public AppBarLayout getAppBarLayout() {
206         return mAppBarLayout;
207     }
208 
autoSetCollapsingToolbarLayoutScrolling()209     private void autoSetCollapsingToolbarLayoutScrolling() {
210         if (mAppBarLayout == null) {
211             return;
212         }
213         final CoordinatorLayout.LayoutParams params =
214                 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
215         final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
216         behavior.setDragCallback(
217                 new AppBarLayout.Behavior.DragCallback() {
218                     @Override
219                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
220                         // Header can be scrolling while device in landscape mode and SDK > 33
221                         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
222                             return false;
223                         } else {
224                             return appBarLayout.getResources().getConfiguration().orientation
225                                     == Configuration.ORIENTATION_LANDSCAPE;
226                         }
227                     }
228                 });
229         params.setBehavior(behavior);
230     }
231 }
232