1/*
2 * Copyright (C) 2016 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 */
16package com.android.settings;
17
18import android.animation.Animator;
19import android.animation.Animator.AnimatorListener;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.support.v4.view.PagerAdapter;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.ViewStub;
27import android.view.ViewStub.OnInflateListener;
28import android.view.animation.AccelerateInterpolator;
29import android.view.animation.DecelerateInterpolator;
30import android.view.animation.Interpolator;
31import android.widget.FrameLayout;
32import android.widget.LinearLayout;
33import android.widget.ScrollView;
34
35/**
36 * A PagerAdapter used by PreviewSeekBarPreferenceFragment that for showing multiple preview screen
37 * regarding a single setting and allowing the user to swipe across them.
38 */
39public class PreviewPagerAdapter extends PagerAdapter {
40
41    /** Duration to use when cross-fading between previews. */
42    private static final long CROSS_FADE_DURATION_MS = 400;
43
44    /** Interpolator to use when cross-fading between previews. */
45    private static final Interpolator FADE_IN_INTERPOLATOR = new DecelerateInterpolator();
46
47    /** Interpolator to use when cross-fading between previews. */
48    private static final Interpolator FADE_OUT_INTERPOLATOR = new AccelerateInterpolator();
49
50    private FrameLayout[] mPreviewFrames;
51
52    private boolean mIsLayoutRtl;
53
54    private Runnable mAnimationEndAction;
55
56    private int mAnimationCounter;
57
58    private boolean[][] mViewStubInflated;
59
60    public PreviewPagerAdapter(Context context, boolean isLayoutRtl,
61            int[] previewSampleResIds, Configuration[] configurations) {
62        mIsLayoutRtl = isLayoutRtl;
63        mPreviewFrames = new FrameLayout[previewSampleResIds.length];
64        mViewStubInflated = new boolean[previewSampleResIds.length][configurations.length];
65
66        for (int i = 0; i < previewSampleResIds.length; ++i) {
67            int p = mIsLayoutRtl ? previewSampleResIds.length - 1 - i : i;
68            mPreviewFrames[p] = new FrameLayout(context);
69            mPreviewFrames[p].setLayoutParams(new LinearLayout.LayoutParams(
70                    LinearLayout.LayoutParams.MATCH_PARENT,
71                    LinearLayout.LayoutParams.MATCH_PARENT));
72
73            for (int j = 0; j < configurations.length; ++j) {
74                // Create a new configuration for the specified value. It won't
75                // have any theme set, so manually apply the current theme.
76                final Context configContext = context.createConfigurationContext(configurations[j]);
77                configContext.getTheme().setTo(context.getTheme());
78
79                final LayoutInflater configInflater = LayoutInflater.from(configContext);
80                final ViewStub sampleViewStub = new ViewStub(configContext);
81                sampleViewStub.setLayoutResource(previewSampleResIds[i]);
82                final int fi = i, fj = j;
83                sampleViewStub.setOnInflateListener(new OnInflateListener() {
84                    @Override
85                    public void onInflate(ViewStub stub, View inflated) {
86                        inflated.setVisibility(stub.getVisibility());
87                        mViewStubInflated[fi][fj] = true;
88                    }
89                });
90
91                mPreviewFrames[p].addView(sampleViewStub);
92            }
93        }
94    }
95
96    @Override
97    public void destroyItem (ViewGroup container, int position, Object object) {
98        container.removeView((View) object);
99    }
100
101    @Override
102    public int getCount() {
103        return mPreviewFrames.length;
104    }
105
106    @Override
107    public Object instantiateItem(ViewGroup container, int position) {
108        container.addView(mPreviewFrames[position]);
109        return mPreviewFrames[position];
110    }
111
112    @Override
113    public boolean isViewFromObject(View view, Object object) {
114        return (view == object);
115    }
116
117    boolean isAnimating() {
118        return mAnimationCounter > 0;
119    }
120
121    void setAnimationEndAction(Runnable action) {
122        mAnimationEndAction = action;
123    }
124
125    void setPreviewLayer(int newLayerIndex, int currentLayerIndex, int currentFrameIndex,
126            final boolean animate) {
127        for (FrameLayout previewFrame : mPreviewFrames) {
128            if (currentLayerIndex >= 0) {
129                final View lastLayer = previewFrame.getChildAt(currentLayerIndex);
130                if (mViewStubInflated[currentFrameIndex][currentLayerIndex]) {
131                    // Explicitly set to INVISIBLE only when the stub has
132                    // already been inflated.
133                    if (previewFrame == mPreviewFrames[currentFrameIndex]) {
134                        setVisibility(lastLayer, View.INVISIBLE, animate);
135                    } else {
136                        setVisibility(lastLayer, View.INVISIBLE, false);
137                    }
138                }
139            }
140
141            // Set next layer visible, as well as inflate necessary views.
142            View nextLayer = previewFrame.getChildAt(newLayerIndex);
143            if (previewFrame == mPreviewFrames[currentFrameIndex]) {
144                // Inflate immediately if the stub has not yet been inflated.
145                if (!mViewStubInflated[currentFrameIndex][newLayerIndex]) {
146                    nextLayer = ((ViewStub) nextLayer).inflate();
147                    nextLayer.setAlpha(0.0f);
148                }
149                setVisibility(nextLayer, View.VISIBLE, animate);
150            } else {
151                setVisibility(nextLayer, View.VISIBLE, false);
152            }
153        }
154    }
155
156    private void setVisibility(final View view, final int visibility, boolean animate) {
157        final float alpha = (visibility == View.VISIBLE ? 1.0f : 0.0f);
158        if (!animate) {
159            view.setAlpha(alpha);
160            view.setVisibility(visibility);
161        } else {
162            final Interpolator interpolator = (visibility == View.VISIBLE ? FADE_IN_INTERPOLATOR
163                    : FADE_OUT_INTERPOLATOR);
164            if (visibility == View.VISIBLE) {
165                // Fade in animation.
166                view.animate()
167                .alpha(alpha)
168                .setInterpolator(FADE_IN_INTERPOLATOR)
169                .setDuration(CROSS_FADE_DURATION_MS)
170                .setListener(new PreviewFrameAnimatorListener())
171                .withStartAction(new Runnable() {
172                    @Override
173                    public void run() {
174                        view.setVisibility(visibility);
175                    }
176                });
177            } else {
178                // Fade out animation.
179                view.animate()
180                .alpha(alpha)
181                .setInterpolator(FADE_OUT_INTERPOLATOR)
182                .setDuration(CROSS_FADE_DURATION_MS)
183                .setListener(new PreviewFrameAnimatorListener())
184                .withEndAction(new Runnable() {
185                    @Override
186                    public void run() {
187                        view.setVisibility(visibility);
188                    }
189                });
190            }
191        }
192    }
193
194    private void runAnimationEndAction() {
195        if (mAnimationEndAction != null && !isAnimating()) {
196            mAnimationEndAction.run();
197            mAnimationEndAction = null;
198        }
199    }
200
201    private class PreviewFrameAnimatorListener implements AnimatorListener {
202        @Override
203        public void onAnimationStart(Animator animation) {
204            mAnimationCounter++;
205        }
206
207        @Override
208        public void onAnimationEnd(Animator animation) {
209            mAnimationCounter--;
210            runAnimationEndAction();
211        }
212
213        @Override
214        public void onAnimationCancel(Animator animation) {
215            // Empty method.
216        }
217
218        @Override
219        public void onAnimationRepeat(Animator animation) {
220            // Empty method.
221        }
222    }
223}
224