预览

在这里插入图片描述

功能

  • 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的点击事件和背景。

详细细节请见我上方链接

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐