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 package com.example.android.intentplayground;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.drawable.Drawable;
21 import android.os.Bundle;
22 import android.util.DisplayMetrics;
23 import android.view.Gravity;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.FrameLayout;
28 import android.widget.ScrollView;
29 
30 import androidx.annotation.IdRes;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.StringRes;
33 import androidx.fragment.app.Fragment;
34 import androidx.viewpager.widget.PagerTitleStrip;
35 import androidx.viewpager.widget.ViewPager;
36 import java.util.LinkedList;
37 import java.util.List;
38 
39 /**
40  * Displays a help overlay over the current activity.
41  */
42 public class ShowcaseFragment extends Fragment {
43     private ViewGroup mRoot;
44     private List<Step> mSteps = new LinkedList<>();
45     private ViewPager mPager;
46     private StepAdapter mAdapter;
47     private ScrollView mScrollView;
48     private View mOldTarget;
49     private Drawable mOldTargetBackground;
50     private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
51     private Runnable mUserOnFinish;
52     private int mIndex = 0;
53     private static final int SCROLL_OFFSET = 50;
54     private static final float HIGHLIGHT_ELEVATION = 4;
55 
56     @Override
onAttach(Context context)57     public void onAttach(Context context) {
58         super.onAttach(context);
59         mAdapter = new StepAdapter(context, mSteps);
60     }
61 
62     @Nullable
63     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)64     public View onCreateView(LayoutInflater inflater,
65                              @Nullable ViewGroup container,
66                              @Nullable Bundle savedInstanceState) {
67         Context context = getContext();
68         mRoot = container;
69         FrameLayout backgroundLayout = new FrameLayout(context);
70         mPager = new ViewPager(context);
71         PagerTitleStrip pagerTitleView = new PagerTitleStrip(context);
72         pagerTitleView.setGravity(Gravity.TOP);
73         ViewPager.LayoutParams params = new ViewPager.LayoutParams();
74         params.width = ViewPager.LayoutParams.MATCH_PARENT;
75         params.height = ViewPager.LayoutParams.MATCH_PARENT;
76         mPager.setLayoutParams(params);
77         backgroundLayout.setLayoutParams(params);
78         params.height = ViewPager.LayoutParams.WRAP_CONTENT;
79         params.isDecor = true;
80         pagerTitleView.setLayoutParams(params);
81         mPager.addView(pagerTitleView);
82         backgroundLayout.addView(mPager);
83         mAdapter.setButtonCallbacks(
84                 /* onFinish */ view -> {
85                     cancel();
86                     mScrollView.scrollTo(0, 0);
87                 },
88                 /* onCancel */ view -> cancel(),
89                 /* onNext */ view -> next()
90         );
91         mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
92             @Override
93             public void onPageScrolled(int i, float v, int i1) {}
94             @Override
95             public void onPageScrollStateChanged(int i) {}
96             @Override
97             public void onPageSelected(int i) {
98                 executeStep(i);
99             }
100         });
101         mPager.setAdapter(mAdapter);
102         return backgroundLayout;
103     }
104 
105     @Override
onStart()106     public void onStart() {
107         super.onStart();
108         // Get display metrics for converting dp to px
109         getActivity().getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
110         // Scroll to and highlight first step
111         executeStep(mIndex);
112     }
113 
114     @Override
onStop()115     public void onStop() {
116         super.onStop();
117         clearHighlight();
118         if (mUserOnFinish != null) mUserOnFinish.run();
119     }
120 
setScroller(ScrollView scroller)121     public void setScroller(ScrollView scroller) {
122         mScrollView = scroller;
123     }
124 
addStep(Step step)125     public void addStep(Step step) {
126         mSteps.add(step);
127         if (mAdapter != null) mAdapter.notifyDataSetChanged();
128     }
129 
addStep(@tringRes int tutorialText, @IdRes int targetView)130     public void addStep(@StringRes int tutorialText, @IdRes int targetView) {
131         addStep(new Step(tutorialText, targetView));
132     }
133 
addStep(@tringRes int tutorialText, @IdRes int targetView, @IdRes int highlightTargetView)134     public void addStep(@StringRes int tutorialText, @IdRes int targetView,
135                         @IdRes int highlightTargetView) {
136         addStep(new Step(tutorialText, targetView, highlightTargetView));
137     }
138 
addStep(@tringRes int tutorialText, @IdRes int targetView, Runnable callback)139     public void addStep(@StringRes int tutorialText,
140                         @IdRes int targetView,
141                         Runnable callback) {
142         addStep(new Step(tutorialText, targetView, callback));
143     }
144 
145     /**
146      * Advances the pager to the next step.
147      */
next()148     public void next() {
149         mPager.setCurrentItem(++mIndex);
150     }
151 
152     /**
153      * Shows the indicated page.
154      * @param i The index of the page to show.
155      */
executeStep(int i)156     private void executeStep(int i) {
157         Step current = mAdapter.getStep(i);
158         View target = mRoot.findViewById(current.targetViewRes);
159         View highlightTarget = current.highlightTargetViewRes != 0 ?
160                 mRoot.findViewById(current.highlightTargetViewRes) : target;
161         target.getParent().requestChildFocus(target, target);
162         mScrollView.smoothScrollTo(0, Float.valueOf(target.getTop()).intValue()
163                 - SCROLL_OFFSET);
164         highlightView(highlightTarget);
165         if (current.callback != null) current.callback.run();
166     }
167 
168     /**
169      * Destroys this fragment.
170      */
cancel()171     public void cancel() {
172         getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
173     }
174 
clearHighlight()175     private void clearHighlight() {
176         if (mOldTarget != null) {
177             mOldTarget.setBackground(mOldTargetBackground);
178             mOldTarget = null;
179         }
180         mRoot.setBackground(null); // Clear root background
181     }
182 
highlightView(View target)183     private void highlightView(View target) {
184         Resources res = getContext().getResources();
185         clearHighlight();
186         mOldTarget = target;
187         mOldTargetBackground = target.getBackground();
188         target.setBackground(res.getDrawable(R.drawable.showcase_background, null /* theme*/));
189         target.setElevation(HIGHLIGHT_ELEVATION * mDisplayMetrics.density);
190         // Dull parent background
191         mRoot.setBackground(res.getDrawable(R.drawable.shade, null /* theme */));
192     }
193 
194     /**
195      * Set a callback to be run in the onStop() method
196      * @param onFinish Callback to be run when the Showcase is finished
197      */
setOnFinish(Runnable onFinish)198     public void setOnFinish(Runnable onFinish) {
199         this.mUserOnFinish = onFinish;
200     }
201 
202     /**
203      * Represents a page in {@link ViewPager}, with associated text to show and a target element
204      * to scroll to.
205      */
206     public class Step {
207         @StringRes public int tutorialText;
208         @IdRes public int targetViewRes;
209         @IdRes public int highlightTargetViewRes;
210 
211         public Runnable callback;
Step(@tringRes int tutorialSentence, @IdRes int targetView)212         public Step(@StringRes int tutorialSentence, @IdRes int targetView) {
213             tutorialText = tutorialSentence;
214             targetViewRes = targetView;
215         }
Step(@tringRes int tutorialSentence, @IdRes int targetView, @IdRes int highlightTargetView)216         public Step(@StringRes int tutorialSentence, @IdRes int targetView,
217                     @IdRes int highlightTargetView) {
218             tutorialText = tutorialSentence;
219             targetViewRes = targetView;
220             highlightTargetViewRes = highlightTargetView;
221         }
Step(@tringRes int tutorialSentence, @IdRes int targetView, Runnable onStepCallback)222         public Step(@StringRes int tutorialSentence, @IdRes int targetView,
223                     Runnable onStepCallback) {
224             tutorialText = tutorialSentence;
225             targetViewRes = targetView;
226             callback = onStepCallback;
227         }
228     }
229 
230 }
231