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