1/* 2 * Copyright (C) 2011 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 17package com.android.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.LayoutTransition; 22import android.animation.TimeInterpolator; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.content.res.TypedArray; 28import android.util.AttributeSet; 29import android.util.Log; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.ViewGroup; 33import android.widget.LinearLayout; 34import com.android.launcher3.R; 35 36import java.util.ArrayList; 37 38public class PageIndicator extends LinearLayout { 39 @SuppressWarnings("unused") 40 private static final String TAG = "PageIndicator"; 41 // Want this to look good? Keep it odd 42 private static final boolean MODULATE_ALPHA_ENABLED = false; 43 44 private LayoutInflater mLayoutInflater; 45 private int[] mWindowRange = new int[2]; 46 private int mMaxWindowSize; 47 48 private ArrayList<PageIndicatorMarker> mMarkers = 49 new ArrayList<PageIndicatorMarker>(); 50 private int mActiveMarkerIndex; 51 52 public static class PageMarkerResources { 53 int activeId; 54 int inactiveId; 55 56 public PageMarkerResources() { 57 activeId = R.drawable.ic_pageindicator_current; 58 inactiveId = R.drawable.ic_pageindicator_default; 59 } 60 public PageMarkerResources(int aId, int iaId) { 61 activeId = aId; 62 inactiveId = iaId; 63 } 64 } 65 66 public PageIndicator(Context context) { 67 this(context, null); 68 } 69 70 public PageIndicator(Context context, AttributeSet attrs) { 71 this(context, attrs, 0); 72 } 73 74 public PageIndicator(Context context, AttributeSet attrs, int defStyle) { 75 super(context, attrs, defStyle); 76 TypedArray a = context.obtainStyledAttributes(attrs, 77 R.styleable.PageIndicator, defStyle, 0); 78 mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15); 79 mWindowRange[0] = 0; 80 mWindowRange[1] = 0; 81 mLayoutInflater = LayoutInflater.from(context); 82 a.recycle(); 83 84 // Set the layout transition properties 85 LayoutTransition transition = getLayoutTransition(); 86 transition.setDuration(175); 87 } 88 89 private void enableLayoutTransitions() { 90 LayoutTransition transition = getLayoutTransition(); 91 transition.enableTransitionType(LayoutTransition.APPEARING); 92 transition.enableTransitionType(LayoutTransition.DISAPPEARING); 93 transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING); 94 transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 95 } 96 97 private void disableLayoutTransitions() { 98 LayoutTransition transition = getLayoutTransition(); 99 transition.disableTransitionType(LayoutTransition.APPEARING); 100 transition.disableTransitionType(LayoutTransition.DISAPPEARING); 101 transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 102 transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 103 } 104 105 void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) { 106 if (activeIndex < 0) { 107 new Throwable().printStackTrace(); 108 } 109 int windowSize = Math.min(mMarkers.size(), mMaxWindowSize); 110 int hWindowSize = (int) windowSize / 2; 111 float hfWindowSize = windowSize / 2f; 112 int windowStart = Math.max(0, activeIndex - hWindowSize); 113 int windowEnd = Math.min(mMarkers.size(), windowStart + mMaxWindowSize); 114 windowStart = windowEnd - Math.min(mMarkers.size(), windowSize); 115 int windowMid = windowStart + (windowEnd - windowStart) / 2; 116 boolean windowAtStart = (windowStart == 0); 117 boolean windowAtEnd = (windowEnd == mMarkers.size()); 118 boolean windowMoved = (mWindowRange[0] != windowStart) || 119 (mWindowRange[1] != windowEnd); 120 121 if (!allowAnimations) { 122 disableLayoutTransitions(); 123 } 124 125 // Remove all the previous children that are no longer in the window 126 for (int i = getChildCount() - 1; i >= 0; --i) { 127 PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i); 128 int markerIndex = mMarkers.indexOf(marker); 129 if (markerIndex < windowStart || markerIndex >= windowEnd) { 130 removeView(marker); 131 } 132 } 133 134 // Add all the new children that belong in the window 135 for (int i = 0; i < mMarkers.size(); ++i) { 136 PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i); 137 if (windowStart <= i && i < windowEnd) { 138 if (indexOfChild(marker) < 0) { 139 addView(marker, i - windowStart); 140 } 141 if (i == activeIndex) { 142 marker.activate(windowMoved); 143 } else { 144 marker.inactivate(windowMoved); 145 } 146 } else { 147 marker.inactivate(true); 148 } 149 150 if (MODULATE_ALPHA_ENABLED) { 151 // Update the marker's alpha 152 float alpha = 1f; 153 if (mMarkers.size() > windowSize) { 154 if ((windowAtStart && i > hWindowSize) || 155 (windowAtEnd && i < (mMarkers.size() - hWindowSize)) || 156 (!windowAtStart && !windowAtEnd)) { 157 alpha = 1f - Math.abs((i - windowMid) / hfWindowSize); 158 } 159 } 160 marker.animate().alpha(alpha).setDuration(500).start(); 161 } 162 } 163 164 if (!allowAnimations) { 165 enableLayoutTransitions(); 166 } 167 168 mWindowRange[0] = windowStart; 169 mWindowRange[1] = windowEnd; 170 } 171 172 void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) { 173 index = Math.max(0, Math.min(index, mMarkers.size())); 174 175 PageIndicatorMarker m = 176 (PageIndicatorMarker) mLayoutInflater.inflate(R.layout.page_indicator_marker, 177 this, false); 178 m.setMarkerDrawables(marker.activeId, marker.inactiveId); 179 180 mMarkers.add(index, m); 181 offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations); 182 } 183 void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) { 184 for (int i = 0; i < markers.size(); ++i) { 185 addMarker(Integer.MAX_VALUE, markers.get(i), allowAnimations); 186 } 187 } 188 189 void updateMarker(int index, PageMarkerResources marker) { 190 PageIndicatorMarker m = mMarkers.get(index); 191 m.setMarkerDrawables(marker.activeId, marker.inactiveId); 192 } 193 194 void removeMarker(int index, boolean allowAnimations) { 195 if (mMarkers.size() > 0) { 196 index = Math.max(0, Math.min(mMarkers.size() - 1, index)); 197 mMarkers.remove(index); 198 offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations); 199 } 200 } 201 void removeAllMarkers(boolean allowAnimations) { 202 while (mMarkers.size() > 0) { 203 removeMarker(Integer.MAX_VALUE, allowAnimations); 204 } 205 } 206 207 void setActiveMarker(int index) { 208 // Center the active marker 209 mActiveMarkerIndex = index; 210 offsetWindowCenterTo(index, false); 211 } 212 213 void dumpState(String txt) { 214 System.out.println(txt); 215 System.out.println("\tmMarkers: " + mMarkers.size()); 216 for (int i = 0; i < mMarkers.size(); ++i) { 217 PageIndicatorMarker m = mMarkers.get(i); 218 System.out.println("\t\t(" + i + ") " + m); 219 } 220 System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]"); 221 System.out.println("\tchildren: " + getChildCount()); 222 for (int i = 0; i < getChildCount(); ++i) { 223 PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i); 224 System.out.println("\t\t(" + i + ") " + m); 225 } 226 System.out.println("\tactive: " + mActiveMarkerIndex); 227 } 228} 229