Android 13 添加Sidebar侧边工具栏 SystemUI
Android 13 SideBar 侧边工具栏的实现
预览

功能
- 1.当处于最初状态时,只在launcher处显示,在锁屏下拉框处不显示,最初状态是一条线line
- 2.当长按line时,会进行宽度的扩展,可以进行上下左右的拖动,且随手释放可以回归左右屏幕侧
- 3.当长按line拖动时,做了背景的区域截取亮度检测,在不同亮暗的背景下可以显示不同的颜色
- 4.当往左右侧滑动line时,这时候打开工具栏,显示出软件的icon,可以快捷点击打开软件,内部竖直滑动
仓库
https://github.com/MKpack/SideBar
这里只进行一些逻辑的讲解,需要源码可以到这里拉取
xml布局文件
frameworks/base/packages/SystemUI/res/layout/side_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 侧边栏 -->
<com.android.systemui.sidebar.SideBarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent">
<com.android.systemui.sidebar.SideBarLineView
android:id="@+id/side_bar_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left | top">
<com.android.systemui.sidebar.SideBarShotCutContainerView
android:id="@+id/side_bar_short_cut_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
</com.android.systemui.sidebar.SideBarLineView>
</com.android.systemui.sidebar.SideBarView>
整体的结构是SideBarView内层有SideBarLine,这个line内部包含了container,container中存放app的列表,SideBarLine可以进行大小的变化去适配container,SideBarView作为window,固定到最大大小,内部只有line进行更新。
dagger依赖注入
一些view的全局对象,可以通过dagger进行注入,创建一个component进行管理
package com.android.systemui.sidebar;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
import dagger.BindsInstance;
import android.content.Context;
import dagger.Subcomponent;
import com.android.systemui.sidebar.SideBarComponent.SideBarScope;
import androidx.annotation.Nullable;
import android.os.Bundle;
@Subcomponent(modules = { SideBarModule.class })
@SideBarComponent.SideBarScope
public interface SideBarComponent {
@Subcomponent.Factory
interface Factory {
SideBarComponent create();
}
SideBar getSideBar();
@Documented
@Retention(RUNTIME)
@Scope
@interface SideBarScope {
}
}
提供依赖的模块SideBarModule.java
package com.android.systemui.sidebar;
import com.android.systemui.sidebar.SideBarComponent.SideBarScope;
import android.content.Context;
import dagger.Module;
import dagger.Provides;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.R;
import android.view.View;
import android.view.LayoutInflater;
/** Module for {@link com.android.systemui.sidebar.SideBarComponent}. */
@Module
public interface SideBarModule {
@Provides
@SideBarScope
static SideBarView providSideBarView(Context context) {
return (SideBarView) LayoutInflater.from(context).inflate(R.layout.side_bar, null);
}
@Provides
@SideBarScope
static SideBarLineView provideBarLine(SideBarView sideBarView) {
return sideBarView.findViewById(R.id.side_bar_line);
}
@Provides
@SideBarScope
static SideBarShotCutContainerView provideShotCutContainer(SideBarView sideBarView) {
return sideBarView.findViewById(R.id.side_bar_short_cut_container);
}
}
SysUIComponent (系统级根组件)
└── SideBarComponent (子组件,@SideBarScope)
├── SideBarView (布局根视图)
├── SideBarLineView (从布局中查找)
├── SideBarShotCutContainerView (从布局中查找)
└── SideBar (最终组装的对象,在component中暴露出来的方法)
然后现在我们没有被sysUIComponent进行管理,找到frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java,找到
@Module(includes = {
...
}, subcomponents = {
...,
FragmentService.FragmentCreator.class,
SideBarComponent.class
})
添加我们的SideBarComponent即可,别忘记import类。
然后是使用,我们可以直接用@Inject注入上面有的这些对象
@Inject
SideBar(Context context,
SideBarView sideBarView,
SideBarLineView barlineView,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
SideBarShotCutContainerView shotCutContainerView,
SideBarTransitions sideBarTransitions)
在sidebarcontroller中,只需要
SideBarComponent component = mSideBarComponentFactory.create();
mSideBar = component.getSideBar();
即可,这里的sidebarcontroller随后说。
如何将它组装到systemui呢。
找到frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java文件
// 创建SideBarController变量
private SideBarController mSideBarController;
//在CentralSurfacesImpl的构造函数中添加SideBarController sideBarController
@Inject
CentralSurfacesImpl(...,
SideBarController sideBarController) {
mSideBarController = sideBarController;
...
}
为什么可以直接注入呢?我们看下SideBarController的构造函数
@Inject
public SideBarController(Context context, SideBarComponent.Factory sideBarComponentFactory,
PanelExpansionStateManager panelExpansionStateManager, KeyguardStateController keyguardStateController) {
mContext = context;
mSideBarComponentFactory = sideBarComponentFactory;
mPanelExpansionStateManager = panelExpansionStateManager;
mPanelExpansionListener = event -> {
// event.getFraction() 是拉开的比例:0.0 是关闭,1.0 是全开
// event.getExpanded() 是布尔值:是否处于展开状态
isPanelExpanded = event.getFraction() > 0f;
updateVisibility();
};
mKeyguardStateController = keyguardStateController;
}```
可以看到也是@Inject的,在看
@SysUISingleton
public class SideBarController implements Callbacks
他是由SysUISingleton全局单例,找到frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
```java
/**
* Scope annotation for singleton items within the SysUIComponent.
*/
@Documented
@Retention(RUNTIME)
@Scope
public @interface SysUISingleton {
}
public interface SysUIComponent {
/**
* Builder for a SysUIComponent.
*/
@SysUISingleton
@Subcomponent.Builder
interface Builder {
可以看到是标注在了SysUIComponent,说明SideBarController也是由这个component自动注入的,为原因。
然后在centeralSurfacesImpl中创建方法
// create sidebar
private void createSideBar() {
mSideBarController.createSideBar();
}
// 在makeStatusBarView中调用即可
createSideBar();
这样即完成了sidebarcontroller的创建,而sidebarcontroller又会创建sidebar,
SideBarComponent component = mSideBarComponentFactory.create();
mSideBar = component.getSideBar();
mSideBar.init();
SideBar继承ViewController,init最后调用oninit方法,完成创建。
不在锁屏和通知栏中显示sidebar
不在通知栏显示使用了PanelExpansionStateManager ,通知面板(Notification Panel)展开状态管理器,sidebarcontroller中自动注入该对象,
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final PanelExpansionListener mPanelExpansionListener;
private boolean isPanelExpanded = false;
@Inject
public SideBarController(...,
PanelExpansionStateManager panelExpansionStateManager) {
...
mPanelExpansionStateManager = panelExpansionStateManager;
mPanelExpansionListener = event -> {
// event.getFraction() 是拉开的比例:0.0 是关闭,1.0 是全开
// event.getExpanded() 是布尔值:是否处于展开状态
isPanelExpanded = event.getFraction() > 0f;
updateVisibility();
};
}
public void createSideBar() {
...
mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener);
}
private void updateVisibility() {
if (isPanelExpanded) {
mSideBar.hide();
return;
}
ComponentName top = getTopActivity();
if (isLauncher(top)) {
mSideBar.show();
} else {
mSideBar.hide();
}
}
在构造函数中拿到对象,并且新建一个监听器,在createSideBar()添加该监听器。用isPanelExpanded去更改sidebar是否显示。
不在锁屏显示:使用了KeyguardStateController
public void createSideBar() {
SideBarComponent component = mSideBarComponentFactory.create();
mSideBar = component.getSideBar();
mSideBar.init();
mKeyguardStateController.addCallback(
new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
updateVisibility();
}
});
mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener);
}
private void updateVisibility() {
if (mKeyguardStateController.isShowing()
|| mKeyguardStateController.isOccluded()) {
mSideBar.hide();
return;
}
if (isPanelExpanded) {
mSideBar.hide();
return;
}
mSideBar.show();
}
注册了keyguardstatecontroller回掉,进行判断是否为锁屏状态。
line的移动逻辑
当line长按后expandWidth之后可以进行拖动
mBarLine.setOnTouchListener((v, event) -> {
// 处理触摸事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下按钮
mDownY = event.getRawY();
mDownX = event.getRawX();
mLongPressRunable = () -> {
if (isShowContainer)
return;
setExpanded(true);
islongPressed = true;
};
mHandler.postDelayed(mLongPressRunable, 1000); // 长按1000ms后执行扩展宽度
case MotionEvent.ACTION_MOVE:
float newDownX = event.getRawX();
float newDownY = event.getRawY();
if (!islongPressed) {
if (mLongPressRunable != null)
mHandler.removeCallbacks(mLongPressRunable);
}
// 滑动展开container
ifExpandToContainer(newDownX);
// 移动按钮
if (islongPressed) {
dragSideBar(newDownX, newDownY);// 如果没有展开,不处理移动事件
}
dragSideBar
private void dragSideBar(float newDownX, float newDownY) {
if (newDownX >= mScreenWidth / 2 && isOnLeftSide || newDownX <= mScreenWidth / 2 && !isOnLeftSide) {
// 如果跨过屏幕中心线,切换侧边
isOnLeftSide = !isOnLeftSide;
switchSide(isOnLeftSide);
Log.d(TAG, "switch side: " + (isOnLeftSide ? "left" : "right"));
} else if (newDownX >= mScreenWidth / 4 && newDownX <= mScreenWidth * 3 / 4) {
// 在1/4-3/4的屏幕内小幅度移动,而超出的更大幅度移动
if (isOnLeftSide) {
windowX += (int) ((newDownX - mDownX) / 7);
updateXPosition(windowX);
} else {
windowX += (int) ((mDownX - newDownX) / 7);
updateXPosition(windowX);
}
} else {
if (isOnLeftSide) {
windowX += (int) ((newDownX - mDownX) / 2);
if (windowX <= mEdgeMarginlf) {
windowX = mEdgeMarginlf;
}
updateXPosition(windowX);
} else {
windowX += (int) ((mDownX - newDownX) / 2);
if (windowX <= mEdgeMarginlf) {
windowX = mEdgeMarginlf;
}
updateXPosition(windowX);
}
}
mDownX = newDownX;
int dy = (int) (newDownY - mDownY);
mDownY = newDownY;
// 向上拖
if (dy < 0) {
if (lineY > dpToPx(rangeY)) {
// line 还没到顶部 → 先动 line
lineY += dy;
if (lineY < dpToPx(rangeY)) {
lineY = dpToPx(rangeY);
}
mBarLine.setTranslationY(lineY);
} else {
// line 已经在顶部 → 开始动 window
windowY += dy;
if (windowY < dpToPx(rangeY)) {
windowY = dpToPx(rangeY);
}
updateYPosition(windowY);
}
}
// 向下拖
else {
// window 没到底
if ((windowY + dpToPx(maxWindowHeight)) < mScreenHeight - dpToPx(10)) {
// window 还能往下,先动 window
windowY += dy;
// 如果新windowy超出底部则设置为底部
if ((windowY + dpToPx(maxWindowHeight)) > mScreenHeight - dpToPx(10)) {
windowY = mScreenHeight - dpToPx(10) - dpToPx(maxWindowHeight);
}
updateYPosition(windowY);
} else {
// window 到底了,开始动 line
lineY += dy;
// line的最底部
int maxLineY = dpToPx(maxWindowHeight) - dpToPx(rangeY) - dpToPx(SIDEBAR_LINE_HEIGHT);
if (lineY > maxLineY) {
lineY = maxLineY;
}
mBarLine.setTranslationY(lineY);
}
}
}
左右移动:前部分移动幅度较大,后部分移动幅度小且到达一定阈值换边,最后会回到固定的左右间距。
上下移动:先是window进行移动即sidebarview,当window到底部时,line开始在window内部进行移动。
line放大缩小的逻辑
private void expandToWindow() {
// judge again
if (isAnimating)
return;
Log.d(TAG, "second filter isAnimating: " + isAnimating);
isAnimating = true;
// 暂停采集
mRegionSamplingHelper.stop();
// mBarLine.setBackgroundColor(Color.TRANSPARENT);
mBarLine.startExpandToWindowOptimization(dpToPx(maxWindowHeight), dpToPx(maxWindowWidth), isOnLeftSide,
new AnimationListener() {
@Override
public void onFinished() {
Log.d(TAG, "onfinished set isAnimating as false");
isShowContainer = true;
isAnimating = false;
switchFlag(false);
}
});
}
private void shrinkToLine() {
// judge again
if (isAnimating)
return;
Log.d(TAG, "second filter isAnimating: " + isAnimating);
isAnimating = true;
mBarLine.startShrinkToLineOptimization(lineY, isOnLeftSide, new AnimationListener() {
@Override
public void onFinished() {
Log.d(TAG, "onfinished set isAnimating as false");
isAnimating = false;
isShowContainer = false;
// 开始采集
mRegionSamplingHelper.start(calculateSamplingRect());
switchFlag(true);
}
});
}
这是在sidebar中的封装方法,真正的方法是在barline中的startExpand和startShrink中
/**
* 对于startExpandTowindow方法的一个优化,不在动画中使用setlayoutparams减少layout的重绘制
*/
public void startExpandToWindowOptimization(int windowHeight, int expandWith, boolean isOnLeftSide,
AnimationListener listener) {
if (currentAnimator != null) {
Log.d(TAG, "shrinkToLine cancel");
currentAnimator.removeAllListeners();
currentAnimator.cancel();
currentAnimator = null;
}
int width = getWidth();
int height = getHeight();
float translationY = getTranslationY();
float targetScaleX = (float) width / expandWith;
float targetScaleY = (float) height / windowHeight;
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
lp.width = expandWith;
lp.height = windowHeight;
setLayoutParams(lp);
if (isOnLeftSide) {
setPivotX(0f); // 水平方向以左边缘为基准(向右生长)
} else {
setPivotX(expandWith); // 水平方向以右边缘为基准 (向左生长)
}
setPivotY(0f); // 垂直方向以顶部为基准(向下生长)
setScaleX(targetScaleX);
setScaleY(targetScaleY);
mLineBackgroundDrawable.setGlassMode(true);
currentAnimator = ValueAnimator.ofFloat(0f, 1f);
currentAnimator.setDuration(400);
currentAnimator.setInterpolator(new FastOutSlowInInterpolator());
currentAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentAnimator = null;
if (listener != null)
listener.onFinished();
isShowContainer = true;
}
});
currentAnimator.addUpdateListener(animation -> {
float f = (float) animation.getAnimatedFraction();
setScaleX(targetScaleX + (1 - targetScaleX) * f);
setScaleY(targetScaleY + (1 - targetScaleY) * f);
setTranslationY(translationY - translationY * f);
// 设置圆角,mRadius->expandRaduis
float currentRaduis = dpToPx(mRadius) - ((dpToPx(mRadius) -
dpToPx(expandRadius)) * f);
mLineBackgroundDrawable.setCornerRadius(currentRaduis);
invalidateOutline();
setAlpha(Math.max(0f, (f - 0.3f) / 0.7f));
// container的显示
if (mContainerView != null) {
float alpha = Math.max(0f, (f - 0.3f) / 0.7f);
mContainerView.setAlpha(alpha);
}
});
currentAnimator.start();
}
/**
* 对于startExpandTowindow方法的一个优化,不在动画中使用setlayoutparams减少layout的重绘制
*
* @param lineY
* @param isOnLeftSide
* @param listener
*/
public void startShrinkToLineOptimization(int lineY, boolean isOnLeftSide, AnimationListener listener) {
if (currentAnimator != null) {
Log.d(TAG, "ExpandToWindow cancel");
currentAnimator.removeAllListeners();
currentAnimator.cancel();
currentAnimator = null;
}
final int startWidth = getWidth();
final int startHeight = getHeight();
final float startTranslationY = getTranslationY();
float targetScaleX = (float) startWidth / dpToPx(SIDEBAR_WIDTH);
float targetScaleY = (float) startHeight / dpToPx(SIDEBAR_HEIGHT);
// 先设置大小
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
lp.width = dpToPx(SIDEBAR_WIDTH);
lp.height = dpToPx(SIDEBAR_HEIGHT);
setLayoutParams(lp);
if (isOnLeftSide) {
setPivotX(0f); // 水平方向以左边缘为基准(向左缩小)
} else {
setPivotX(dpToPx(SIDEBAR_WIDTH)); // 水平方向以右边缘为基准 (向右缩小)
}
setPivotY(0f); // 垂直方向以顶部为基准(向上缩小)
// container 的隐藏
if (mContainerView != null) {
mContainerView.setAlpha(0f);
}
// 然后立马放大
setScaleX(targetScaleX);
setScaleY(targetScaleY);
Log.d(TAG, "targetScaleX: " + targetScaleX + ", targetScaleY: " + targetScaleY);
currentAnimator = ValueAnimator.ofFloat(0f, 1f);
currentAnimator.setDuration(300);
currentAnimator.setInterpolator(new FastOutSlowInInterpolator());
currentAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentAnimator = null;
if (listener != null)
listener.onFinished();
isShowContainer = false;
mLineBackgroundDrawable.setGlassMode(false);
mLineBackgroundDrawable.setCornerRadius(dpToPx(mRadius));
mContainerView.recyclerViewScrollToPosition(0);
}
});
currentAnimator.addUpdateListener(animation -> {
float f = (float) animation.getAnimatedFraction();
setScaleX(targetScaleX + (1 - targetScaleX) * f);
setScaleY(targetScaleY + (1 - targetScaleY) * f);
// Y 偏移
setTranslationY(startTranslationY + (lineY - startTranslationY) * f);
// // 设置圆角,expandRadius->mRadius
// float currentRaduis = dpToPx(expandRadius) - ((dpToPx(expandRadius) -
// dpToPx(mRadius)) * f);
// mLineBackgroundDrawable.setCornerRadius(currentRaduis);
// invalidateOutline();
});
currentAnimator.start();
}
这里的之前有个没有optimization的方法,之前是通过改变layoutparams进行改变的,比较卡顿,而优化是通过放大缩小进行的,动画比较流畅。可以去看我原文件。
在line状态下可以自动识别背景亮度并且改变背景颜色
通过RegionSamplingHelper实现,在package com.android.systemui.shared.navigationbar自带了所以直接使用了。
private RegionSamplingHelper mRegionSamplingHelper;
// 在构造函数中new
mRegionSamplingHelper = new RegionSamplingHelper(mView,
new SamplingCallback() {
@Override
public void onRegionDarknessChanged(boolean isRegionDark) {
Log.d(TAG, "isRegionDark: " + isRegionDark);
mSideBarTransitions.getLightBa mSideBarTransitions.getLightBarTransitionsController().setIconsDark(!isRegionDark, true);
rTransitionsController().setIconsDark(!isRegionDark, true);
}
@Override
public Rect getSampledRegion(View sampledView) {
return calculateSamplingRect();
}
@Override
public boolean isSamplingEnabled() {
return true;
}
}, mainExecutor, bgExecutor);
// 该方法是计算需要检测的矩阵的,这里可以看到是line的大小
private Rect calculateSamplingRect() {
int margin = dpToPx(4);
int[] pos = new int[2];
mBarLine.getLocationOnScreen(pos);
Rect rect = new Rect(pos[0] - margin, pos[1],
pos[0] + mBarLine.getWidth() + margin,
pos[1] + mBarLine.getHeight());
// Log.d(TAG, "rect.left: " + rect.left + ", rect.top: " + rect.top + ",
// rect.right: " + rect.right
// + ", rect.bottom: " + rect.bottom);
return rect;
}
然后需要启动方法,和停止方法。
mRegionSamplingHelper.setWindowVisible(true);
// 开始采集
mRegionSamplingHelper.start(calculateSamplingRect());
// 停止采集
mRegionSamplingHelper.stop();
然后是他调用了什么呢?可以看到 mSideBarTransitions.getLightBarTransitionsController().setIconsDark(!isRegionDark, true);
这个方法,进入该方法
public LightBarTransitionsController getLightBarTransitionsController() {
return mLightBarTransitionsController;
}
LightBarTransitionsController
public void setIconsDark(boolean dark, boolean animate) {
if (!animate) {
setIconTintInternal(dark ? 1.0f : 0.0f);
mNextDarkIntensity = dark ? 1.0f : 0.0f;
} else if (mTransitionPending) {
deferIconTintChange(dark ? 1.0f : 0.0f);
} else if (mTransitionDeferring) {
animateIconTint(dark ? 1.0f : 0.0f,
Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
mTransitionDeferringDuration);
} else {
animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration());
}
}
发现最后都是会进入applyDarkIntensity
public interface DarkIntensityApplier {
void applyDarkIntensity(float darkIntensity);
int getTintAnimationDuration();
}
然后回到SideBarTransitions,继承了implements
LightBarTransitionsController.DarkIntensityApplier 这个接口,然后也实现了该方法
@Override
public void applyDarkIntensity(float darkIntensity) {
mLineView.setDarkIntensity(darkIntensity);
}
----->
// SideBarLineView
public void setDarkIntensity(float darkIntensity) {
if (mDarkIntensity == darkIntensity) {
return;
}
mDarkIntensity = darkIntensity;
mLineBackgroundDrawable.setDarkIntensity(mDarkIntensity);
}
------>
// mLineBackgroundDrawable
public void setDarkIntensity(float darkIntensity) {
lastDarkIntensity = darkIntensity;
int color = ColorUtils.blendARGB(mLightIconColor, mDarkIconColor,
darkIntensity);
Log.d(TAG, "mLightIconColor" + mLightIconColor + ", mDarkIconColor" + mDarkIconColor);
mPaint.setColor(color);
mPaint.setAlpha((int) (255 * (1f - darkIntensity * 0.3f)));
invalidateSelf();
}
mlineBackgroundDrawable是SidebarLineView的background。由这个链条去改变了line的颜色。
当我触摸line之外而window之内的区域时,没事件且无法传递到下一层解决方法
可以使用ViewTreeObserver.OnComputeInternalInsetsListener对触摸区域进行设置。
// 设置触控模式
mInsetsListener = new ViewTreeObserver.OnComputeInternalInsetsListener() {
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo insets) {
// 设置触控模式为区域模式
insets.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
// 清空并设置新的区域
insets.touchableRegion.setEmpty();
int[] loc = new int[2];
mBarLine.getLocationInWindow(loc);
Rect touchRect = new Rect(loc[0], loc[1], loc[0] + mBarLine.getWidth(), loc[1] + mBarLine.getHeight());
// 定义 line 的区域
insets.touchableRegion.set(touchRect);
Log.d("SideBar", "Width: " + mBarLine.getWidth() + " Height: " + mBarLine.getHeight());
Log.d(TAG, "rect.top: " + touchRect.top + ", rect.bottom: " + touchRect.bottom + ", rect.left: "
+ touchRect.left + ", rect.right: " + touchRect.right);
}
};
然后就是在viewattached时候注册该listener
// 在初始化或者 onAttachedToWindow 时注册
private void setupTouchableRegion() {
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
}
// 在 onDetachedFromWindow 时移除,防止内存泄漏
private void removeTouchableRegion() {
mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
}
当展开显示container后触摸如何传递给container
view自带的dispacthTouchEvent会在分发之前调用该方法,如果为true则消费该事件,如果是false则下发给子view。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!isShowContainer)
return true;
return false;
}
如何拿到所有app的图标,并进行显示
private void loadsIcon() {
// get apps information
LauncherApps launcherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
apps = launcherApps.getActivityList(null,
Process.myUserHandle());
Log.d(TAG, "apps: " + apps.size());
// mIcons = new ArrayList<>();
// for (LauncherActivityInfo appInfo : apps) {
// Log.d(TAG, "app: " + appInfo.getComponentName().getPackageName());
// Drawable icon = appInfo.getBadgedIcon(0);
// if (icon != null)
// mIcons.add(wrapIconWithWhiteBackground(icon));
// }
// Log.d(TAG, "mIcons size: " + mIcons.size());
mRecyclerView.setAdapter(new AppAdapter());
}
通过launcherApps可以拿到所有的App信息。
然后我们可以自己写一个AppAdapter
private class AppAdapter extends RecyclerView.Adapter<AppAdapter.Holder> {
private class Holder extends RecyclerView.ViewHolder {
ImageView icon;
Holder(FrameLayout rootItem, ImageView iconView) {
super(rootItem);
icon = iconView;
}
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
// item中先设置一个layout当作item,里面内容可以做到水平居中
FrameLayout itemRoot = new FrameLayout(getContext());
RecyclerView.LayoutParams lRootp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
itemRoot.setLayoutParams(lRootp);
ImageView imageView = new ImageView(parent.getContext());
int size = dpToPx(36);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(size, size);
lp.gravity = Gravity.CENTER_HORIZONTAL;
int margin = dpToPx(8);
lp.setMargins(0, margin, 0, margin);
imageView.setLayoutParams(lp);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
itemRoot.addView(imageView);
return new Holder(itemRoot, imageView);
}
@Override
public void onBindViewHolder(Holder holder, int position) {
LauncherActivityInfo appInfo = apps.get(position);
Drawable icon = appInfo.getBadgedIcon(0);
holder.icon.setImageDrawable(wrapIconWithWhiteBackground(icon));
holder.icon.setOnClickListener(v -> {
if (listener != null) {
listener.launchApp(appInfo);
}
});
}
@Override
public int getItemCount() {
return apps.size();
}
}
内部如果加一个framelayout可以将icon设置水平居中,然后wrapIconWithBackground是用来给Icon加白色背景的。在onBindViewHolder中设置icon的点击事件和背景。
详细细节请见我上方链接
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)