diff options
21 files changed, 1625 insertions, 41 deletions
diff --git a/Android.mk b/Android.mk index 1f98b76d8..c7e2dfde1 100644 --- a/Android.mk +++ b/Android.mk @@ -23,7 +23,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional -LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13 +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13 \ + android-support-v7-recyclerview LOCAL_SRC_FILES := $(call all-java-files-under, src) \ $(call all-java-files-under, WallpaperPicker/src) \ diff --git a/res/drawable-nodpi/letter_indicator.9.png b/res/drawable-nodpi/letter_indicator.9.png Binary files differnew file mode 100644 index 000000000..af3578ece --- /dev/null +++ b/res/drawable-nodpi/letter_indicator.9.png diff --git a/res/drawable/scrubber_back.xml b/res/drawable/scrubber_back.xml new file mode 100644 index 000000000..56cf5af73 --- /dev/null +++ b/res/drawable/scrubber_back.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#99333333"/> + <corners android:radius="5dip"/> +</shape> diff --git a/res/drawable/seek_back.xml b/res/drawable/seek_back.xml new file mode 100644 index 000000000..a49796c05 --- /dev/null +++ b/res/drawable/seek_back.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="line"> + <stroke android:width="4dp" android:color="#FFF"/> + <size android:height="2dp" /> +</shape> diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 8cd867366..4c097ba0a 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -62,5 +62,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> + + <include layout="@layout/app_drawer_container" + android:id="@+id/app_drawer_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> </com.android.launcher3.DragLayer> </FrameLayout> diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index ad1027829..e193e4820 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -83,5 +83,10 @@ android:id="@+id/overview_panel" android:visibility="gone" /> + <include layout="@layout/app_drawer_container" + android:id="@+id/app_drawer_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> </com.android.launcher3.DragLayer> </FrameLayout> diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml index 62615411f..772110527 100644 --- a/res/layout-sw720dp/launcher.xml +++ b/res/layout-sw720dp/launcher.xml @@ -81,5 +81,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> + + <include layout="@layout/app_drawer_container" + android:id="@+id/app_drawer_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> </com.android.launcher3.DragLayer> </FrameLayout> diff --git a/res/layout/app_drawer_container.xml b/res/layout/app_drawer_container.xml new file mode 100644 index 000000000..c0eb97a99 --- /dev/null +++ b/res/layout/app_drawer_container.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/fake_page_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> + <FrameLayout + android:id="@+id/fake_page" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" + android:clipToPadding="false" /> + </FrameLayout> +</FrameLayout>
\ No newline at end of file diff --git a/res/layout/app_drawer_item.xml b/res/layout/app_drawer_item.xml new file mode 100644 index 000000000..37bdeea6a --- /dev/null +++ b/res/layout/app_drawer_item.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:autofit="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:splitMotionEvents="false" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <LinearLayout + android:layout_marginTop="10dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" > + <com.android.launcher3.AutoFitTextView + android:id="@+id/drawer_item_title" + android:layout_width="30dp" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp" + android:includeFontPadding="false" + android:gravity="start|top" + android:singleLine="true" + autofit:minTextSize="8sp" + android:textSize="35sp" + android:textColor="@android:color/white"/> + <LinearLayout + android:id="@+id/drawer_item_flow" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" /> + </LinearLayout> + <Space android:id="@+id/spacer" + android:layout_width="match_parent" + android:layout_height="0dp" + android:visibility="gone" /> +</LinearLayout> diff --git a/res/layout/drawer_icon.xml b/res/layout/drawer_icon.xml new file mode 100644 index 000000000..1cdea7884 --- /dev/null +++ b/res/layout/drawer_icon.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher3.AppDrawerIconView + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + + <TextView + android:id="@+id/label" + style="@style/WorkspaceIcon" + android:shadowRadius="0" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + +</com.android.launcher3.AppDrawerIconView> diff --git a/res/layout/scrub_layout.xml b/res/layout/scrub_layout.xml new file mode 100644 index 000000000..e8761e306 --- /dev/null +++ b/res/layout/scrub_layout.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_marginRight="20dp" + android:layout_marginLeft="20dp" + android:layout_height="wrap_content"> + + <LinearLayout + android:clickable="true" + android:layout_marginTop="-5dp" + android:layout_below="@+id/scrubberIndicator" + android:orientation="horizontal" + android:background="@drawable/scrubber_back" + android:layout_width="match_parent" + android:layout_height="50dp"> + + <Space + android:layout_weight="0.1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + + <TextView + android:id="@+id/firstSection" + android:gravity="center" + android:textColor="@android:color/white" + android:paddingRight="10dp" + android:textStyle="bold" + style="?android:attr/textAppearanceLarge" + android:layout_width="wrap_content" + android:layout_height="match_parent" /> + + <LinearLayout + android:layout_weight="0.9" + android:layout_width="0dp" + android:layout_gravity="center" + android:orientation="vertical" + android:layout_height="match_parent"> + + <SeekBar + android:id="@+id/scrubber" + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:layout_marginRight="-10dp" + android:layout_marginLeft="-10dp" + android:thumb="@android:color/transparent" + android:thumbOffset="-10dp" + android:progressDrawable="@drawable/seek_back" + android:layout_width="match_parent" + android:layout_gravity="center" + android:layout_height="match_parent" /> + + </LinearLayout> + + + <TextView + android:gravity="center" + android:id="@+id/lastSection" + android:paddingLeft="10dp" + android:textColor="@android:color/white" + android:textStyle="bold" + style="?android:attr/textAppearanceLarge" + android:layout_width="wrap_content" + android:layout_height="match_parent" /> + + <Space + android:layout_weight="0.1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + + </LinearLayout> + + <TextView + android:id="@+id/scrubberIndicator" + android:background="@drawable/letter_indicator" + android:layout_width="80dp" + android:textSize="30sp" + android:gravity="center" + android:textColor="@android:color/white" + android:clickable="false" + android:layout_marginBottom="-20dp" + android:visibility="invisible" + android:layout_height="100dp" /> + +</RelativeLayout> diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 240f31171..b32c5a2bc 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -154,4 +154,9 @@ <attr name="overlay" format="boolean"/> </declare-styleable> + <declare-styleable name="AutofitTextView"> + <attr name="minTextSize" format="dimension" /> + <attr name="precision" format="float" /> + <attr name="sizeToFit" format="boolean" /> + </declare-styleable> </resources> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d591535d3..7ceaf8bd5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -118,4 +118,9 @@ <dimen name="grid_padding">15dp</dimen> <dimen name="grid_custom_text">50dp</dimen> + + <dimen name="scrubber_bottom_padding">30dp</dimen> + + <!-- Vertical app drawer padding --> + <dimen name="vertical_app_drawer_icon_padding">5px</dimen> </resources> diff --git a/src/com/android/launcher3/AppDrawerIconView.java b/src/com/android/launcher3/AppDrawerIconView.java new file mode 100644 index 000000000..d8564b6ed --- /dev/null +++ b/src/com/android/launcher3/AppDrawerIconView.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * AppDrawerIconView - represents icons in the vertical app drawer. + * Found to be more performant than the BubbleTextView used in the + * legacy app drawer. + */ +public class AppDrawerIconView extends LinearLayout { + + TextView mLabel; + ImageView mIcon; + + public AppDrawerIconView(Context context) { + super(context); + } + + public AppDrawerIconView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AppDrawerIconView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLabel = (TextView) findViewById(R.id.label); + mIcon = (ImageView) findViewById(R.id.image); + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); + mLabel.setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f, + BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + setAlpha(PagedViewIcon.PRESS_ALPHA); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + setAlpha(1f); + break; + } + return super.onTouchEvent(event); + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/AppDrawerListAdapter.java b/src/com/android/launcher3/AppDrawerListAdapter.java new file mode 100644 index 000000000..891c32357 --- /dev/null +++ b/src/com/android/launcher3/AppDrawerListAdapter.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.ComponentName; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.support.v7.widget.RecyclerView; +import android.widget.LinearLayout; +import android.widget.SectionIndexer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; + +/** + * AppDrawerListAdapter - list adapter for the vertical app drawer + */ +public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdapter.ViewHolder> + implements View.OnLongClickListener, DragSource, SectionIndexer { + + private static final int SCRUBBER_MARGIN_FROM_BOTTOM_DP = 80; + private static final char NUMERIC_OR_SPECIAL_CHAR = '#'; + private static final String NUMERIC_OR_SPECIAL_HEADER = "#"; + + private ArrayList<AppItemIndexedInfo> mHeaderList; + private LayoutInflater mLayoutInflater; + + private Launcher mLauncher; + private DeviceProfile mDeviceProfile; + private LinkedHashMap<String, Integer> mSectionHeaders; + private LinearLayout.LayoutParams mIconParams, mSpacerParams; + private Rect mIconRect; + + public static class ViewHolder extends RecyclerView.ViewHolder { + public AutoFitTextView mTextView; + public ViewGroup mLayout; + public View mSpacer; + public ViewHolder(View itemView) { + super(itemView); + mTextView = (AutoFitTextView) itemView.findViewById(R.id.drawer_item_title); + mLayout = (ViewGroup) itemView.findViewById(R.id.drawer_item_flow); + mSpacer = itemView.findViewById(R.id.spacer); + } + } + + public AppDrawerListAdapter(Launcher launcher) { + mLauncher = launcher; + mHeaderList = new ArrayList<AppItemIndexedInfo>(); + mDeviceProfile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + mLayoutInflater = LayoutInflater.from(launcher); + initParams(); + } + + private void initParams() { + mIconParams = new + LinearLayout.LayoutParams(mDeviceProfile.folderCellWidthPx, + ViewGroup.LayoutParams.WRAP_CONTENT); + mSpacerParams = new + LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + (int) Utilities.convertDpToPixel(SCRUBBER_MARGIN_FROM_BOTTOM_DP, mLauncher)); + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + mIconRect = new Rect(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx); + } + + /** + * Create and populate mHeaderList (buckets for app sorting) + * @param info + */ + public void populateByCharacter(ArrayList<AppInfo> info) { + if (info == null || info.size() <= 0) { + return; + } + + // Create a clone of AppInfo ArrayList to preserve data + ArrayList<AppInfo> tempInfo = new ArrayList<AppInfo>(info.size()); + for (AppInfo i : info) { + tempInfo.add(i); + } + + ListIterator<AppInfo> it = tempInfo.listIterator(); + ArrayList<AppInfo> appInfos = new ArrayList<AppInfo>(); + appInfos.clear(); + + // get next app + AppInfo app = it.next(); + + // get starting character + boolean isSpecial = false; + char startChar = app.title.toString().toUpperCase().charAt(0); + if (!Character.isLetter(startChar)) { + isSpecial = true; + } + + // now iterate through + for (AppInfo info1 : tempInfo) { + char newChar = info1.title.toString().toUpperCase().charAt(0); + // if same character + if (newChar == startChar) { + // add it + appInfos.add(info1); + } else if (isSpecial && !Character.isLetter(newChar)) { + appInfos.add(info1); + } + } + + for (int i = 0; i < appInfos.size(); i += mDeviceProfile.numColumnsBase) { + int endIndex = (int) Math.min(i + mDeviceProfile.numColumnsBase, appInfos.size()); + ArrayList<AppInfo> subList = new ArrayList<AppInfo>(appInfos.subList(i, endIndex)); + AppItemIndexedInfo indexInfo; + if (isSpecial) { + indexInfo = new AppItemIndexedInfo('#', subList, i != 0); + } else { + indexInfo = new AppItemIndexedInfo(startChar, subList, i != 0); + } + mHeaderList.add(indexInfo); + } + + for (AppInfo remove : appInfos) { + // remove from mApps + tempInfo.remove(remove); + } + populateByCharacter(tempInfo); + } + + public void setApps(ArrayList<AppInfo> list) { + if (!LauncherAppState.isDisableAllApps()) { + mHeaderList.clear(); + Collections.sort(list, LauncherModel.getAppNameComparator()); + populateByCharacter(list); + populateSectionHeaders(); + mLauncher.updateScrubber(); + this.notifyDataSetChanged(); + } + } + + private void populateSectionHeaders() { + if (mSectionHeaders == null || mSectionHeaders.size() != mHeaderList.size()) { + mSectionHeaders = new LinkedHashMap<String, Integer>(); + } + int count = 0; + for (int i = 0; i < mHeaderList.size(); i++) { + AppItemIndexedInfo info = mHeaderList.get(i); + if (!mHeaderList.get(i).isChild) { + mSectionHeaders.put(String.valueOf(mHeaderList.get(i).mChar), count); + } + if (info.mInfo.size() < mDeviceProfile.numColumnsBase) { + count++; + } else { + count += info.mInfo.size() / mDeviceProfile.numColumnsBase; + } + } + } + + private void reset() { + ArrayList<AppInfo> infos = getAllApps(); + setApps(infos); + } + + private ArrayList<AppInfo> getAllApps() { + ArrayList<AppInfo> indexedInfos = new ArrayList<AppInfo>(); + + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + for (AppInfo info : indexedInfo.mInfo) { + indexedInfos.add(info); + } + } + return indexedInfos; + } + + public void updateApps(ArrayList<AppInfo> list) { + // We remove and re-add the updated applications list because it's properties may have + // changed (ie. the title), and this will ensure that the items will be in their proper + // place in the list. + if (!LauncherAppState.isDisableAllApps()) { + removeAppsWithoutInvalidate(list); + addAppsWithoutInvalidate(list); + reset(); + } + } + + + public void addApps(ArrayList<AppInfo> list) { + if (!LauncherAppState.isDisableAllApps()) { + addAppsWithoutInvalidate(list); + reset(); + } + } + + private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) { + // We add it in place, in alphabetical order + int count = list.size(); + for (int i = 0; i < count; ++i) { + AppInfo info = list.get(i); + boolean found = false; + AppItemIndexedInfo lastInfoForSection = null; + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + if (info.title.charAt(0) == indexedInfo.mChar) { + Collections.sort(indexedInfo.mInfo, LauncherModel.getAppNameComparator()); + int index = + Collections.binarySearch(indexedInfo.mInfo, + info, LauncherModel.getAppNameComparator()); + if (index >= 0) { + found = true; + break; + } else { + lastInfoForSection = indexedInfo; + } + } + } + if (!found) { + if (lastInfoForSection != null) { + lastInfoForSection.mInfo.add(info); + } else { + // we need to create a new section + ArrayList<AppInfo> newInfos = new ArrayList<AppInfo>(); + newInfos.add(info); + AppItemIndexedInfo newInfo = + new AppItemIndexedInfo(info.title.charAt(0), newInfos, false); + mHeaderList.add(newInfo); + } + } + } + } + + public void removeApps(ArrayList<AppInfo> appInfos) { + if (!LauncherAppState.isDisableAllApps()) { + removeAppsWithoutInvalidate(appInfos); + //recreate everything + reset(); + } + } + + private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) { + // loop through all the apps and remove apps that have the same component + int length = list.size(); + for (int i = 0; i < length; ++i) { + AppInfo info = list.get(i); + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + ArrayList<AppInfo> clonedIndexedInfoApps = + (ArrayList<AppInfo>) indexedInfo.mInfo.clone(); + int index = + findAppByComponent(clonedIndexedInfoApps, info); + if (index > -1) { + indexedInfo.mInfo.remove(info); + } + } + } + } + + private int findAppByComponent(List<AppInfo> list, AppInfo item) { + ComponentName removeComponent = item.intent.getComponent(); + int length = list.size(); + for (int i = 0; i < length; ++i) { + AppInfo info = list.get(i); + if (info.intent.getComponent().equals(removeComponent)) { + return i; + } + } + return -1; + } + + /* + * AllAppsView implementation + */ + public void setup(Launcher launcher) { + mLauncher = launcher; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()). + inflate(R.layout.app_drawer_item, parent, false); + ViewHolder holder = new ViewHolder(v); + holder.mSpacer.setLayoutParams(mSpacerParams); + for (int i = 0; i < mDeviceProfile.numColumnsBase; i++) { + AppDrawerIconView icon = (AppDrawerIconView) mLayoutInflater.inflate( + R.layout.drawer_icon, holder.mLayout, false); + icon.setLayoutParams(mIconParams); + icon.setOnClickListener(mLauncher); + icon.setOnLongClickListener(this); + int padding = (int) mLauncher.getResources() + .getDimension(R.dimen.vertical_app_drawer_icon_padding); + icon.setPadding(padding, padding, padding, padding); + holder.mLayout.addView(icon); + } + return holder; + } + + @Override + public int getItemCount() { + return mHeaderList.size(); + } + + public AppItemIndexedInfo getItemAt(int position) { + if (position < mHeaderList.size()) + return mHeaderList.get(position); + return null; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(position); + holder.mTextView.setVisibility(indexedInfo.isChild ? View.INVISIBLE : View.VISIBLE); + if (!indexedInfo.isChild) { + if (indexedInfo.mChar == NUMERIC_OR_SPECIAL_CHAR) { + holder.mTextView.setText(NUMERIC_OR_SPECIAL_HEADER); + } else { + holder.mTextView.setText(String.valueOf(indexedInfo.mChar)); + } + } + final int size = indexedInfo.mInfo.size(); + for (int i = 0; i < holder.mLayout.getChildCount(); i++) { + AppDrawerIconView icon = (AppDrawerIconView) holder.mLayout.getChildAt(i); + if (i >= size) { + icon.setVisibility(View.INVISIBLE); + } else { + icon.setVisibility(View.VISIBLE); + AppInfo info = indexedInfo.mInfo.get(i); + icon.setTag(info); + Drawable d = Utilities.createIconDrawable(info.iconBitmap); + d.setBounds(mIconRect); + icon.mIcon.setImageDrawable(d); + icon.mLabel.setText(info.title); + } + } + if (position == getItemCount() - 1) { + holder.mSpacer.setVisibility(View.VISIBLE); + } else { + holder.mSpacer.setVisibility(View.GONE); + } + holder.itemView.setTag(indexedInfo); + } + + @Override + public boolean onLongClick(View v) { + if (v instanceof AppDrawerIconView) { + beginDraggingApplication(v); + mLauncher.showWorkspace(); + } + return false; + } + + @Override + public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, + boolean success) { + // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling + if (isFlingToDelete) return; + + endDragging(target, false, success); + + // Display an error message if the drag failed due to there not being enough space on the + // target layout we were dropping on. + if (!success) { + boolean showOutOfSpaceMessage = false; + if (target instanceof Workspace) { + int currentScreen = mLauncher.getCurrentWorkspaceScreen(); + Workspace workspace = (Workspace) target; + CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); + ItemInfo itemInfo = (ItemInfo) d.dragInfo; + if (layout != null) { + layout.calculateSpans(itemInfo); + showOutOfSpaceMessage = + !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); + } + } + if (showOutOfSpaceMessage) { + mLauncher.showOutOfSpaceMessage(false); + } + + d.deferDragViewCleanupPostAnimation = false; + } + } + + /** + * Clean up after dragging. + * + * @param target where the item was dragged to (can be null if the item was flung) + */ + private void endDragging(View target, boolean isFlingToDelete, boolean success) { + if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && + !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { + // Exit spring loaded mode if we have not successfully dropped or have not handled the + // drop in Workspace + mLauncher.getWorkspace().removeExtraEmptyScreenDelayed(true, new Runnable() { + @Override + public void run() { + mLauncher.exitSpringLoadedDragMode(); + mLauncher.unlockScreenOrientation(false); + } + }, 0, true); + } else { + mLauncher.unlockScreenOrientation(false); + } + } + + @Override + public boolean supportsFlingToDelete() { + return false; + } + + @Override + public boolean supportsAppInfoDropTarget() { + return true; + } + + @Override + public boolean supportsDeleteDropTarget() { + return false; + } + + @Override + public float getIntrinsicIconScaleFactor() { + return (float) mDeviceProfile.allAppsIconSizePx / mDeviceProfile.iconSizePx; + } + + private void beginDraggingApplication(View v) { + // mLauncher.getWorkspace().onDragStartedWithItem(v); + mLauncher.getWorkspace().beginDragShared(v, this); + } + + @Override + public void onFlingToDeleteCompleted() { + // We just dismiss the drag when we fling, so cleanup here + } + + public class AppItemIndexedInfo { + private boolean isChild; + private char mChar; + private ArrayList<AppInfo> mInfo; + + private AppItemIndexedInfo(char startChar, ArrayList<AppInfo> info, boolean isChild) { + this.mChar = startChar; + this.mInfo = info; + this.isChild = isChild; + } + + public char getChar() { + return mChar; + } + } + + @Override + public Object[] getSections() { + return mSectionHeaders.keySet().toArray(new String[mSectionHeaders.size()]); + } + + @Override + public int getPositionForSection(int sectionIndex) { + return mSectionHeaders.get(getSections()[sectionIndex]); + } + + @Override + public int getSectionForPosition(int position) { + return mSectionHeaders.get(mHeaderList.get(position).mChar); + } +} diff --git a/src/com/android/launcher3/AppDrawerScrubber.java b/src/com/android/launcher3/AppDrawerScrubber.java new file mode 100644 index 000000000..41c3199b5 --- /dev/null +++ b/src/com/android/launcher3/AppDrawerScrubber.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.util.AttributeSet; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.LinearLayoutManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SectionIndexer; +import android.widget.SeekBar; +import android.widget.TextView; + +public class AppDrawerScrubber extends LinearLayout implements OnClickListener { + + private final int SCRUBBER_INDICATOR_DISPLAY_DURATION = 200; + private final float SCRUBBER_INDICATOR_DISPLAY_TRANSLATIONY = 20f; + + private AppDrawerListAdapter mAdapter; + private RecyclerView mListView; + private TextView mFirstIndicator, mLastIndicator; + private TextView mScrubberIndicator; + private SeekBar mSeekBar; + private String[] mSections; + private LinearLayoutManager mLayoutManager; + + public AppDrawerScrubber(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.scrub_layout, this); + mFirstIndicator = ((TextView) findViewById(R.id.firstSection)); + mFirstIndicator.setOnClickListener(this); + mLastIndicator = ((TextView) findViewById(R.id.lastSection)); + mLastIndicator.setOnClickListener(this); + mScrubberIndicator = (TextView) findViewById(R.id.scrubberIndicator); + mSeekBar = (SeekBar) findViewById(R.id.scrubber); + init(); + } + + public void updateSections() { + mSections = (String[]) mAdapter.getSections(); + mSeekBar.setMax(mSections.length - 1); + mFirstIndicator.setText(mSections[0]); + mLastIndicator.setText(mSections[mSections.length - 1]); + } + + public void setSource(RecyclerView listView) { + mListView = listView; + mAdapter = (AppDrawerListAdapter) listView.getAdapter(); + mLayoutManager = (LinearLayoutManager) listView.getLayoutManager(); + } + + private boolean isReady() { + return mListView != null && + mAdapter != null && + mSections != null; + } + + private void init() { + mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) { + if (!isReady()) { + return; + } + resetScrubber(); + mScrubberIndicator.setTranslationX((progress * seekBar.getWidth()) / + mSections.length); + String section = String.valueOf(mSections[progress]); + mLayoutManager.scrollToPositionWithOffset( + mAdapter.getPositionForSection(progress), 0); + mScrubberIndicator.setText(section); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (!isReady()) { + return; + } + resetScrubber(); + mScrubberIndicator.setAlpha(1f); + mScrubberIndicator.setVisibility(View.VISIBLE); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (!isReady()) { + return; + } + resetScrubber(); + mScrubberIndicator.animate() + .alpha(0f) + .translationYBy(SCRUBBER_INDICATOR_DISPLAY_TRANSLATIONY) + .setDuration(SCRUBBER_INDICATOR_DISPLAY_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mScrubberIndicator.setVisibility(View.INVISIBLE); + } + }); + } + + private void resetScrubber() { + mScrubberIndicator.animate().cancel(); + mScrubberIndicator.setTranslationY(0f); + } + }); + } + + @Override + public void onClick(View v) { + if (v == mFirstIndicator) { + int positionForFirstSection = mAdapter.getPositionForSection(0); + mLayoutManager.scrollToPositionWithOffset(positionForFirstSection, 0); + } else if (v == mLastIndicator) { + int positionForLastSection = mAdapter.getPositionForSection(mSections.length - 1); + mLayoutManager.scrollToPositionWithOffset(positionForLastSection, 0); + } + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/AutoFitTextView.java b/src/com/android/launcher3/AutoFitTextView.java new file mode 100644 index 000000000..208dd4073 --- /dev/null +++ b/src/com/android/launcher3/AutoFitTextView.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2014 Grantland Chew + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * A TextView that resizes it's text to be no larger than the width of the view. + * + * @author Grantland Chew <grantlandchew@gmail.com> + */ +public class AutoFitTextView extends TextView { + + private static final String TAG = "AutoFitTextView"; + private static final boolean SPEW = false; + + // Minimum size of the text in pixels + private static final int DEFAULT_MIN_TEXT_SIZE = 8; //sp + // How precise we want to be when reaching the target textWidth size + private static final float PRECISION = 0.5f; + + // Attributes + private boolean mSizeToFit; + private int mMaxLines; + private float mMinTextSize; + private float mMaxTextSize; + private float mPrecision; + private TextPaint mPaint; + + public AutoFitTextView(Context context) { + super(context); + init(context, null, 0); + } + + public AutoFitTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public AutoFitTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity; + boolean sizeToFit = true; + int minTextSize = (int) scaledDensity * DEFAULT_MIN_TEXT_SIZE; + float precision = PRECISION; + + if (attrs != null) { + TypedArray ta = context.obtainStyledAttributes( + attrs, + R.styleable.AutofitTextView, + defStyle, + 0); + sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit); + minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize, + minTextSize); + precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision); + ta.recycle(); + } + + mPaint = new TextPaint(); + setSizeToFit(sizeToFit); + setRawTextSize(super.getTextSize()); + setRawMinTextSize(minTextSize); + setPrecision(precision); + } + + // Getters and Setters + + /** + * @return whether or not the text will be automatically resized to fit its constraints. + */ + public boolean isSizeToFit() { + return mSizeToFit; + } + + /** + * Sets the property of this field (singleLine, to automatically resize the text to fit its constraints. + */ + public void setSizeToFit() { + setSizeToFit(true); + } + + /** + * If true, the text will automatically be resized to fit its constraints; if false, it will + * act like a normal TextView. + * + * @param sizeToFit + */ + public void setSizeToFit(boolean sizeToFit) { + mSizeToFit = sizeToFit; + refitText(); + } + + /** + * {@inheritDoc} + */ + @Override + public float getTextSize() { + return mMaxTextSize; + } + + /** + * {@inheritDoc} + */ + @Override + public void setTextSize(int unit, float size) { + Context context = getContext(); + Resources r = Resources.getSystem(); + + if (context != null) { + r = context.getResources(); + } + + setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics())); + } + + private void setRawTextSize(float size) { + if (size != mMaxTextSize) { + mMaxTextSize = size; + refitText(); + } + } + + /** + * @return the minimum size (in pixels) of the text size in this AutofitTextView + */ + public float getMinTextSize() { + return mMinTextSize; + } + + /** + * Set the minimum text size to a given unit and value. See TypedValue for the possible + * dimension units. + * + * @param unit The desired dimension unit. + * @param minSize The desired size in the given units. + * + * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize + */ + public void setMinTextSize(int unit, float minSize) { + Context context = getContext(); + Resources r = Resources.getSystem(); + + if (context != null) { + r = context.getResources(); + } + + setRawMinTextSize(TypedValue.applyDimension(unit, minSize, r.getDisplayMetrics())); + } + + /** + * Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size + * is adjusted based on the current density and user font size preference. + * + * @param minSize The scaled pixel size. + * + * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize + */ + public void setMinTextSize(int minSize) { + setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize); + } + + private void setRawMinTextSize(float minSize) { + if (minSize != mMinTextSize) { + mMinTextSize = minSize; + refitText(); + } + } + + /** + * @return the amount of precision used to calculate the correct text size to fit within it's + * bounds. + */ + public float getPrecision() { + return mPrecision; + } + + /** + * Set the amount of precision used to calculate the correct text size to fit within it's + * bounds. Lower precision is more precise and takes more time. + * + * @param precision The amount of precision. + */ + public void setPrecision(float precision) { + if (precision != mPrecision) { + mPrecision = precision; + refitText(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setLines(int lines) { + super.setLines(lines); + mMaxLines = lines; + refitText(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getMaxLines() { + return mMaxLines; + } + + /** + * {@inheritDoc} + */ + @Override + public void setMaxLines(int maxLines) { + super.setMaxLines(maxLines); + if (maxLines != mMaxLines) { + mMaxLines = maxLines; + refitText(); + } + } + + /** + * Re size the font so the specified text fits in the text box assuming the text box is the + * specified width. + */ + private void refitText() { + if (!mSizeToFit) { + return; + } + + if (mMaxLines <= 0) { + // Don't auto-size since there's no limit on lines. + return; + } + + CharSequence text = getText(); + TransformationMethod method = getTransformationMethod(); + if (method != null) { + text = method.getTransformation(text, this); + } + int targetWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + if (targetWidth > 0) { + Context context = getContext(); + Resources r = Resources.getSystem(); + DisplayMetrics displayMetrics; + + float size = mMaxTextSize; + float high = size; + float low = 0; + + if (context != null) { + r = context.getResources(); + } + displayMetrics = r.getDisplayMetrics(); + + mPaint.set(getPaint()); + mPaint.setTextSize(size); + + if ((mMaxLines == 1 && mPaint.measureText(text, 0, text.length()) > targetWidth) + || getLineCount(text, mPaint, size, targetWidth, displayMetrics) > mMaxLines) { + size = getTextSize(text, mPaint, targetWidth, mMaxLines, low, high, mPrecision, + displayMetrics); + } + + if (size < mMinTextSize) { + size = mMinTextSize; + } + + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); + } + } + + /** + * Recursive binary search to find the best size for the text + */ + private static float getTextSize(CharSequence text, TextPaint paint, + float targetWidth, int maxLines, + float low, float high, float precision, + DisplayMetrics displayMetrics) { + float mid = (low + high) / 2.0f; + int lineCount = 1; + StaticLayout layout = null; + + paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid, + displayMetrics)); + + if (maxLines != 1) { + layout = new StaticLayout(text, paint, (int)targetWidth, Layout.Alignment.ALIGN_NORMAL, + 1.0f, 0.0f, true); + lineCount = layout.getLineCount(); + } + + if (SPEW) Log.d(TAG, "low=" + low + " high=" + high + " mid=" + mid + + " target=" + targetWidth + " maxLines=" + maxLines + " lineCount=" + lineCount); + + if (lineCount > maxLines) { + return getTextSize(text, paint, targetWidth, maxLines, low, mid, precision, + displayMetrics); + } + else if (lineCount < maxLines) { + return getTextSize(text, paint, targetWidth, maxLines, mid, high, precision, + displayMetrics); + } + else { + float maxLineWidth = 0; + if (maxLines == 1) { + maxLineWidth = paint.measureText(text, 0, text.length()); + } else { + for (int i = 0; i < lineCount; i++) { + if (layout.getLineWidth(i) > maxLineWidth) { + maxLineWidth = layout.getLineWidth(i); + } + } + } + + if ((high - low) < precision) { + return low; + } else if (maxLineWidth > targetWidth) { + return getTextSize(text, paint, targetWidth, maxLines, low, mid, precision, + displayMetrics); + } else if (maxLineWidth < targetWidth) { + return getTextSize(text, paint, targetWidth, maxLines, mid, high, precision, + displayMetrics); + } else { + return mid; + } + } + } + + private static int getLineCount(CharSequence text, TextPaint paint, float size, float width, + DisplayMetrics displayMetrics) { + paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size, + displayMetrics)); + StaticLayout layout = new StaticLayout(text, paint, (int)width, + Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); + return layout.getLineCount(); + } + + @Override + protected void onTextChanged(final CharSequence text, final int start, + final int lengthBefore, final int lengthAfter) { + super.onTextChanged(text, start, lengthBefore, lengthAfter); + refitText(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (w != oldw) { + refitText(); + } + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index a368796bd..d0fb5c45a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -42,11 +42,11 @@ public class BubbleTextView extends TextView { private static SparseArray<Theme> sPreloaderThemes = new SparseArray<>(2); - private static final float SHADOW_LARGE_RADIUS = 4.0f; - private static final float SHADOW_SMALL_RADIUS = 1.75f; - private static final float SHADOW_Y_OFFSET = 2.0f; - private static final int SHADOW_LARGE_COLOUR = 0xDD000000; - private static final int SHADOW_SMALL_COLOUR = 0xCC000000; + public static final float SHADOW_LARGE_RADIUS = 4.0f; + public static final float SHADOW_SMALL_RADIUS = 1.75f; + public static final float SHADOW_Y_OFFSET = 2.0f; + public static final int SHADOW_LARGE_COLOUR = 0xDD000000; + public static final int SHADOW_SMALL_COLOUR = 0xCC000000; static final float PADDING_V = 3.0f; private HolographicOutlineHelper mOutlineHelper; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 8de95e9ff..d2ba9d28e 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -70,6 +70,8 @@ import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; import android.speech.RecognizerIntent; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -297,6 +299,9 @@ public class Launcher extends Activity OverviewSettingsPanel mOverviewSettingsPanel; private View mAllAppsButton; + protected RecyclerView mAppDrawer; + private AppDrawerListAdapter mAppDrawerAdapter; + private AppDrawerScrubber mScrubber; protected SearchDropTargetBar mSearchDropTargetBar; private AppsCustomizeTabHost mAppsCustomizeTabHost; @@ -596,6 +601,26 @@ public class Launcher extends Activity protected void populateCustomContentContainer() { } + private void initializeScrubber() { + if (mScrubber == null) { + mScrubber = new AppDrawerScrubber(this); + int scrubberPadding = getResources() + .getDimensionPixelSize(R.dimen.scrubber_bottom_padding); + mScrubber.setPadding(0, 0, 0, scrubberPadding); + mScrubber.setGravity(Gravity.BOTTOM); + mScrubber.setSource(mAppDrawer); + } + } + + public void updateScrubber() { + mScrubber.updateSections(); + } + + public void initializeAdapter() { + mAppDrawerAdapter = new AppDrawerListAdapter(this); + mAppDrawerAdapter.notifyDataSetChanged(); + } + /** * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to * ensure the custom content page is added or removed if necessary. @@ -1644,6 +1669,9 @@ public class Launcher extends Activity mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); mAppsCustomizeContent.setup(this, dragController); + // Setup AppDrawer + setupAppDrawer(); + // Setup the drag controller (drop targets have to be added in reverse order in priority) dragController.setDragScoller(mWorkspace); dragController.setScrollView(mDragLayer); @@ -1669,6 +1697,22 @@ public class Launcher extends Activity } } + private void setupAppDrawer() { + if (mAppDrawer == null) { + FrameLayout view = (FrameLayout) findViewById(R.id.app_drawer_container); + mAppDrawer = new RecyclerView(this); + mAppDrawer.setLayoutManager(new LinearLayoutManager(this)); + if (mAppDrawerAdapter == null) { + initializeAdapter(); + } + mAppDrawer.setHasFixedSize(true); + mAppDrawer.setAdapter(mAppDrawerAdapter); + view.addView(mAppDrawer); + initializeScrubber(); + view.addView(mScrubber); + } + } + /** * Sets the all apps button. This method is called from {@link Hotseat}. */ @@ -3205,7 +3249,7 @@ public class Launcher extends Activity return false; } - boolean startActivitySafely(View v, Intent intent, Object tag) { + public boolean startActivitySafely(View v, Intent intent, Object tag) { boolean success = false; if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); @@ -3594,7 +3638,13 @@ public class Launcher extends Activity final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); final View fromView = mWorkspace; - final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; + final View toView; + + if (contentType == AppsCustomizePagedView.ContentType.Applications) { + toView = (FrameLayout) findViewById(R.id.app_drawer_container); + } else { + toView = mAppsCustomizeTabHost; + } final ArrayList<View> layerViews = new ArrayList<View>(); @@ -3616,20 +3666,18 @@ public class Launcher extends Activity final AppsCustomizePagedView content = (AppsCustomizePagedView) toView.findViewById(R.id.apps_customize_pane_content); - final View page = content.getPageAt(content.getCurrentPage()); + final View page = content != null ? content.getPageAt(content.getCurrentPage()) : null; final View revealView = toView.findViewById(R.id.fake_page); final float initialPanelAlpha = 1f; final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets; - if (isWidgetTray) { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); - } else { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); - } + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); // Hide the real page background, and swap in the fake one - content.setPageBackgroundsVisible(false); + if (content != null) { + content.setPageBackgroundsVisible(false); + } revealView.setVisibility(View.VISIBLE); // We need to hide this view as the animation start will be posted. revealView.setAlpha(0); @@ -3695,11 +3743,13 @@ public class Launcher extends Activity } View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator); - pageIndicators.setAlpha(0.01f); - ObjectAnimator indicatorsAlpha = - ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f); - indicatorsAlpha.setDuration(revealDuration); - mStateAnimation.play(indicatorsAlpha); + if (pageIndicators != null) { + pageIndicators.setAlpha(0.01f); + ObjectAnimator indicatorsAlpha = + ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f); + indicatorsAlpha.setDuration(revealDuration); + mStateAnimation.play(indicatorsAlpha); + } if (material) { final View allApps = getAllAppsButton(); @@ -3737,7 +3787,9 @@ public class Launcher extends Activity if (page != null) { page.setLayerType(View.LAYER_TYPE_NONE, null); } - content.setPageBackgroundsVisible(true); + if (content != null) { + content.setPageBackgroundsVisible(true); + } // Hide the search bar if (mSearchDropTargetBar != null) { @@ -3830,7 +3882,14 @@ public class Launcher extends Activity final float scaleFactor = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); - final View fromView = mAppsCustomizeTabHost; + final View fromView; + + if (mAppsCustomizeContent.getContentType() != AppsCustomizePagedView.ContentType.Widgets) { + fromView = (FrameLayout) findViewById(R.id.app_drawer_container); + } else { + fromView = mAppsCustomizeTabHost; + } + final View toView = mWorkspace; Animator workspaceAnim = null; final ArrayList<View> layerViews = new ArrayList<View>(); @@ -3856,10 +3915,10 @@ public class Launcher extends Activity final AppsCustomizePagedView content = (AppsCustomizePagedView) fromView.findViewById(R.id.apps_customize_pane_content); - final View page = content.getPageAt(content.getNextPage()); + final View page = content != null ? content.getPageAt(content.getNextPage()) : null; // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases - int count = content.getChildCount(); + int count = content != null ? content.getChildCount() : 0; for (int i = 0; i < count; i++) { View child = content.getChildAt(i); if (child != page) { @@ -3872,15 +3931,12 @@ public class Launcher extends Activity // don't perform all these no-op animations. In particularly, this was causing // the all-apps button to pop in and out. if (fromView.getVisibility() == View.VISIBLE) { - AppsCustomizePagedView.ContentType contentType = content.getContentType(); + AppsCustomizePagedView.ContentType contentType = + mAppsCustomizeContent.getContentType(); final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets; - if (isWidgetTray) { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); - } else { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); - } + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); int width = revealView.getMeasuredWidth(); int height = revealView.getMeasuredHeight(); @@ -3888,7 +3944,9 @@ public class Launcher extends Activity // Hide the real page background, and swap in the fake one revealView.setVisibility(View.VISIBLE); - content.setPageBackgroundsVisible(false); + if (content != null) { + content.setPageBackgroundsVisible(false); + } final View allAppsButton = getAllAppsButton(); revealView.setTranslationY(0); @@ -3957,12 +4015,14 @@ public class Launcher extends Activity } View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator); - pageIndicators.setAlpha(1f); - ObjectAnimator indicatorsAlpha = - LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f); - indicatorsAlpha.setDuration(revealDuration); - indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f)); - mStateAnimation.play(indicatorsAlpha); + if (pageIndicators != null) { + pageIndicators.setAlpha(1f); + ObjectAnimator indicatorsAlpha = + LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f); + indicatorsAlpha.setDuration(revealDuration); + indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f)); + mStateAnimation.play(indicatorsAlpha); + } width = revealView.getMeasuredWidth(); @@ -4011,9 +4071,11 @@ public class Launcher extends Activity if (page != null) { page.setLayerType(View.LAYER_TYPE_NONE, null); } - content.setPageBackgroundsVisible(true); + if (content != null) { + content.setPageBackgroundsVisible(true); + } // Unhide side pages - int count = content.getChildCount(); + int count = content != null ? content.getChildCount() : 0; for (int i = 0; i < count; i++) { View child = content.getChildAt(i); child.setVisibility(View.VISIBLE); @@ -4025,7 +4087,9 @@ public class Launcher extends Activity page.setTranslationY(0); page.setAlpha(1); } - content.setCurrentPage(content.getNextPage()); + if (content != null) { + content.setCurrentPage(content.getNextPage()); + } mAppsCustomizeContent.updateCurrentPageScroll(); } @@ -4676,6 +4740,7 @@ public class Launcher extends Activity if (!LauncherAppState.isDisableAllApps() && addedApps != null && mAppsCustomizeContent != null) { mAppsCustomizeContent.addApps(addedApps); + mAppDrawerAdapter.addApps(addedApps); } } @@ -5059,6 +5124,9 @@ public class Launcher extends Activity LauncherModel.getSortedWidgetsAndShortcuts(this)); } } else { + if (mAppDrawerAdapter != null) { + mAppDrawerAdapter.setApps(apps); + } if (mAppsCustomizeContent != null) { mAppsCustomizeContent.setApps(apps); mAppsCustomizeContent.onPackagesUpdated( @@ -5089,6 +5157,7 @@ public class Launcher extends Activity if (!LauncherAppState.isDisableAllApps() && mAppsCustomizeContent != null) { mAppsCustomizeContent.updateApps(apps); + mAppDrawerAdapter.updateApps(apps); } } @@ -5168,6 +5237,7 @@ public class Launcher extends Activity if (!LauncherAppState.isDisableAllApps() && mAppsCustomizeContent != null) { mAppsCustomizeContent.removeApps(appInfos); + mAppDrawerAdapter.removeApps(appInfos); } } @@ -5425,16 +5495,24 @@ public class Launcher extends Activity if (mWorkspace != null) mWorkspace.setAlpha(1f); if (mHotseat != null) mHotseat.setAlpha(1f); if (mPageIndicators != null) mPageIndicators.setAlpha(1f); - if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false); + showSearch(); } void hideWorkspaceSearchAndHotseat() { if (mWorkspace != null) mWorkspace.setAlpha(0f); if (mHotseat != null) mHotseat.setAlpha(0f); if (mPageIndicators != null) mPageIndicators.setAlpha(0f); + hideSearch(); + } + + void hideSearch() { if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false); } + void showSearch() { + if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false); + } + public ItemInfo createAppDragInfo(Intent appLaunchIntent) { // Called from search suggestion, not supported in other profiles. final UserHandleCompat myUser = UserHandleCompat.myUserHandle(); diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java new file mode 100644 index 000000000..660251e31 --- /dev/null +++ b/src/com/android/launcher3/PagedViewIcon.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * An icon on a PagedView, specifically for items in the launcher's paged view (with compound + * drawables on the top). + */ +public class PagedViewIcon extends TextView { + /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */ + public static interface PressedCallback { + void iconPressed(PagedViewIcon icon); + } + + @SuppressWarnings("unused") + private static final String TAG = "PagedViewIcon"; + static final float PRESS_ALPHA = 0.4f; + + private PagedViewIcon.PressedCallback mPressedCallback; + private boolean mLockDrawableState = false; + + private Bitmap mIcon; + + public PagedViewIcon(Context context) { + this(context, null); + } + + public PagedViewIcon(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void onFinishInflate() { + super.onFinishInflate(); + + // Ensure we are using the right text size + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); + } + + public void applyFromApplicationInfo(AppInfo info, boolean scaleUp, + PagedViewIcon.PressedCallback cb) { + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + + mIcon = info.iconBitmap; + mPressedCallback = cb; + Drawable icon = Utilities.createIconDrawable(mIcon); + icon.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx); + setCompoundDrawables(null, icon, null, null); + setCompoundDrawablePadding(grid.iconDrawablePaddingPx); + setText(info.title); + setTag(info); + } + + public void lockDrawableState() { + mLockDrawableState = true; + } + + public void resetDrawableState() { + mLockDrawableState = false; + post(new Runnable() { + @Override + public void run() { + refreshDrawableState(); + } + }); + } + + protected void drawableStateChanged() { + super.drawableStateChanged(); + + // We keep in the pressed state until resetDrawableState() is called to reset the press + // feedback + if (isPressed()) { + setAlpha(PRESS_ALPHA); + if (mPressedCallback != null) { + mPressedCallback.iconPressed(this); + } + } else if (!mLockDrawableState) { + setAlpha(1f); + } + } + + @Override + public void draw(Canvas canvas) { + // If text is transparent, don't draw any shadow + if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) { + getPaint().clearShadowLayer(); + super.draw(canvas); + return; + } + + // We enhance the shadow by drawing the shadow twice + getPaint().setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f, + BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR); + super.draw(canvas); + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), + getScrollX() + getWidth(), + getScrollY() + getHeight(), Region.Op.INTERSECT); + getPaint().setShadowLayer(BubbleTextView.SHADOW_SMALL_RADIUS, 0.0f, 0.0f, + BubbleTextView.SHADOW_SMALL_COLOUR); + super.draw(canvas); + canvas.restore(); + } +} diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index addd74cfc..fcd4b8587 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -41,6 +41,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; import android.os.Build; +import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -558,4 +559,11 @@ public final class Utilities { } return null; } + + public static float convertDpToPixel(float dp, Context context){ + Resources resources = context.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + float px = dp * (metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT); + return px; + } } |