mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 11:18:21 +00:00
- Making the view span the full width so that you can grab the scroller on the edge of the screen. - Offsetting the fast-scoll popup so that you can see it as you scrub. Change-Id: If1b1934bbeac0660d829cfc29c9e588df927c5e5
379 lines
13 KiB
Java
379 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2015 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.content.res.Resources;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.text.Editable;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.util.AttributeSet;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.EditText;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.TextView;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
/**
|
|
* The all apps list view container.
|
|
*/
|
|
public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher,
|
|
TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
|
|
View.OnLongClickListener {
|
|
|
|
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
|
|
|
|
private static final int GRID_LAYOUT = 0;
|
|
private static final int LIST_LAYOUT = 1;
|
|
private static final int USE_LAYOUT = GRID_LAYOUT;
|
|
|
|
private Launcher mLauncher;
|
|
private AlphabeticalAppsList mApps;
|
|
private RecyclerView.Adapter mAdapter;
|
|
private RecyclerView.LayoutManager mLayoutManager;
|
|
private RecyclerView.ItemDecoration mItemDecoration;
|
|
private AppsContainerRecyclerView mAppsListView;
|
|
private EditText mSearchBar;
|
|
private int mNumAppsPerRow;
|
|
private Point mLastTouchDownPos = new Point();
|
|
private Rect mPadding = new Rect();
|
|
private int mContentMarginStart;
|
|
|
|
public AppsContainerView(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public AppsContainerView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
this(context, attrs, defStyleAttr, 0);
|
|
}
|
|
|
|
public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
super(context, attrs, defStyleAttr, defStyleRes);
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
|
Resources res = context.getResources();
|
|
|
|
mLauncher = (Launcher) context;
|
|
mApps = new AlphabeticalAppsList(context);
|
|
if (USE_LAYOUT == GRID_LAYOUT) {
|
|
mNumAppsPerRow = grid.appsViewNumCols;
|
|
AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
|
|
mLauncher, this);
|
|
adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
|
|
mLayoutManager = adapter.getLayoutManager(context);
|
|
mItemDecoration = adapter.getItemDecoration();
|
|
mAdapter = adapter;
|
|
mContentMarginStart = adapter.getContentMarginStart();
|
|
} else if (USE_LAYOUT == LIST_LAYOUT) {
|
|
mNumAppsPerRow = 1;
|
|
AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
|
|
adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
|
|
mLayoutManager = adapter.getLayoutManager(context);
|
|
mAdapter = adapter;
|
|
}
|
|
mApps.setAdapter(mAdapter);
|
|
}
|
|
|
|
/**
|
|
* Sets the current set of apps.
|
|
*/
|
|
public void setApps(List<AppInfo> apps) {
|
|
mApps.setApps(apps);
|
|
}
|
|
|
|
/**
|
|
* Adds new apps to the list.
|
|
*/
|
|
public void addApps(List<AppInfo> apps) {
|
|
mApps.addApps(apps);
|
|
}
|
|
|
|
/**
|
|
* Updates existing apps in the list
|
|
*/
|
|
public void updateApps(List<AppInfo> apps) {
|
|
mApps.updateApps(apps);
|
|
}
|
|
|
|
/**
|
|
* Removes some apps from the list.
|
|
*/
|
|
public void removeApps(List<AppInfo> apps) {
|
|
mApps.removeApps(apps);
|
|
}
|
|
|
|
/**
|
|
* Scrolls this list view to the top.
|
|
*/
|
|
public void scrollToTop() {
|
|
mAppsListView.scrollToPosition(0);
|
|
}
|
|
|
|
/**
|
|
* Returns the content view used for the launcher transitions.
|
|
*/
|
|
public View getContentView() {
|
|
return findViewById(R.id.apps_list);
|
|
}
|
|
|
|
/**
|
|
* Returns the reveal view used for the launcher transitions.
|
|
*/
|
|
public View getRevealView() {
|
|
return findViewById(R.id.apps_view_transition_overlay);
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
|
LAYOUT_DIRECTION_RTL);
|
|
if (USE_LAYOUT == GRID_LAYOUT) {
|
|
((AppsGridAdapter) mAdapter).setRtl(isRtl);
|
|
}
|
|
mSearchBar = (EditText) findViewById(R.id.app_search_box);
|
|
mSearchBar.addTextChangedListener(this);
|
|
mSearchBar.setOnEditorActionListener(this);
|
|
mAppsListView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
|
|
mAppsListView.setApps(mApps);
|
|
mAppsListView.setNumAppsPerRow(mNumAppsPerRow);
|
|
mAppsListView.setLayoutManager(mLayoutManager);
|
|
mAppsListView.setAdapter(mAdapter);
|
|
mAppsListView.setHasFixedSize(true);
|
|
if (isRtl) {
|
|
mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(),
|
|
mAppsListView.getPaddingRight() + mContentMarginStart,
|
|
mAppsListView.getPaddingBottom());
|
|
} else {
|
|
mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart,
|
|
mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight(),
|
|
mAppsListView.getPaddingBottom());
|
|
}
|
|
if (mItemDecoration != null) {
|
|
mAppsListView.addItemDecoration(mItemDecoration);
|
|
}
|
|
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
|
|
getPaddingBottom());
|
|
}
|
|
|
|
@Override
|
|
public void setInsets(Rect insets) {
|
|
setPadding(mPadding.left + insets.left, mPadding.top + insets.top,
|
|
mPadding.right + insets.right, mPadding.bottom + insets.bottom);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent ev) {
|
|
if (ev.getAction() == MotionEvent.ACTION_DOWN ||
|
|
ev.getAction() == MotionEvent.ACTION_MOVE) {
|
|
mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(View v) {
|
|
// Return early if this is not initiated from a touch
|
|
if (!v.isInTouchMode()) return false;
|
|
// When we have exited all apps or are in transition, disregard long clicks
|
|
if (!mLauncher.isAppsViewVisible() ||
|
|
mLauncher.getWorkspace().isSwitchingState()) return false;
|
|
// Return if global dragging is not enabled
|
|
if (!mLauncher.isDraggingEnabled()) return false;
|
|
|
|
// Start the drag
|
|
mLauncher.getWorkspace().beginDragShared(v, mLastTouchDownPos, this, false);
|
|
|
|
// We delay entering spring-loaded mode slightly to make sure the UI
|
|
// thready is free of any work.
|
|
postDelayed(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// We don't enter spring-loaded mode if the drag has been cancelled
|
|
if (mLauncher.getDragController().isDragging()) {
|
|
// Go into spring loaded mode (must happen before we startDrag())
|
|
mLauncher.enterSpringLoadedDragMode();
|
|
}
|
|
}
|
|
}, 150);
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean supportsFlingToDelete() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean supportsAppInfoDropTarget() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean supportsDeleteDropTarget() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public float getIntrinsicIconScaleFactor() {
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
|
return (float) grid.allAppsIconSizePx / grid.iconSizePx;
|
|
}
|
|
|
|
@Override
|
|
public void onFlingToDeleteCompleted() {
|
|
// We just dismiss the drag when we fling, so cleanup here
|
|
mLauncher.exitSpringLoadedDragModeDelayed(true,
|
|
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
mLauncher.unlockScreenOrientation(false);
|
|
}
|
|
|
|
@Override
|
|
public void onDropCompleted(View target, DropTarget.DragObject d, 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.exitSpringLoadedDragModeDelayed(true,
|
|
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
}
|
|
mLauncher.unlockScreenOrientation(false);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
// Do nothing
|
|
}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
// Do nothing
|
|
}
|
|
|
|
@Override
|
|
public void afterTextChanged(final Editable s) {
|
|
if (s.toString().isEmpty()) {
|
|
mApps.setFilter(null);
|
|
} else {
|
|
String formatStr = getResources().getString(R.string.apps_view_no_search_results);
|
|
if (USE_LAYOUT == GRID_LAYOUT) {
|
|
((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
|
|
s.toString()));
|
|
} else {
|
|
((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
|
|
s.toString()));
|
|
}
|
|
|
|
final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
|
|
mApps.setFilter(new AlphabeticalAppsList.Filter() {
|
|
@Override
|
|
public boolean retainApp(AppInfo info) {
|
|
String title = info.title.toString();
|
|
String sectionName = mApps.getSectionNameForApp(info);
|
|
return sectionName.toLowerCase().contains(filterText) ||
|
|
title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
|
|
List<AppInfo> appsWithoutSections = mApps.getAppsWithoutSectionBreaks();
|
|
List<AppInfo> apps = mApps.getApps();
|
|
if (appsWithoutSections.size() == 1) {
|
|
mAppsListView.getChildAt(apps.indexOf(appsWithoutSections.get(0))).performClick();
|
|
InputMethodManager imm = (InputMethodManager)
|
|
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public View getContent() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
|
|
if (!toWorkspace) {
|
|
// Disable the focus so that the search bar doesn't get focus
|
|
mSearchBar.setFocusableInTouchMode(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
|
|
// Do nothing
|
|
}
|
|
|
|
@Override
|
|
public void onLauncherTransitionStep(Launcher l, float t) {
|
|
// Do nothing
|
|
}
|
|
|
|
@Override
|
|
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
|
|
if (toWorkspace) {
|
|
// Clear the search bar
|
|
mSearchBar.setText("");
|
|
} else {
|
|
mSearchBar.setFocusableInTouchMode(true);
|
|
}
|
|
}
|
|
}
|