android14的下拉栏定制

将android14的下拉栏进行修改,要求实现

android14的下拉栏定制_第1张图片

要实现这种效果

1.修改tile的形状

要将形状从之前的长方形改成圆形我们需要对他找到他生成tile的地方,他是通过

diff --git a/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 79260897..3061ddec 100644
--- a/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -122,6 +122,7 @@ public class QSFactoryImpl implements QSFactory {
     @Override
     public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) {
         QSIconView icon = tile.createTileView(context);
-        return new QSTileViewImpl(context, icon, collapsedView);
+         Log.w("xsr_spec", "No stock tile spec: " + tile.getTileSpec());
+        return new QSTileViewBak(context, icon, collapsedView); 
     }
 }

所以我们只需要在这里修改逻辑使用自己的方法就可以改变形状,我这里搬运了android11的图标实现 

diff --git a/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
new file mode 100644
index 00000000..f2db08c5
--- /dev/null
+++ b/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2017 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.systemui.qs.tileimpl;
+
+import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.service.quicksettings.Tile;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.Switch;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+
+import android.graphics.drawable.shapes.OvalShape;
+
+public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
+
+    private static final String TAG = "QSTileBaseView";
+    private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask;
+    private final H mHandler = new H();
+    private final int[] mLocInScreen = new int[2];
+    private final FrameLayout mIconFrame;
+    protected QSIconView mIcon;
+    protected RippleDrawable mRipple;
+    private Drawable mTileBackground;
+    private String mAccessibilityClass;
+    private boolean mTileState;
+    private boolean mCollapsedView;
+    private boolean mShowRippleEffect = true;
+    private float mStrokeWidthActive;
+    private float mStrokeWidthInactive;
+
+    private final ImageView mBg;
+    private final int mColorActive;
+    private final int mColorInactive;
+    private final int mColorDisabled;
+    private int mCircleColor;
+    private int mBgSize;
+ int position ;
+
+    public QSTileBaseView(Context context, QSIconView icon) {
+        this(context, icon, false);
+    }
+
+    public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) {
+        super(context);
+        // Default to Quick Tile padding, and QSTileView will specify its own padding.
+        int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
+        mIconFrame = new FrameLayout(context);
+        mStrokeWidthActive = context.getResources()
+                .getDimension(com.android.internal.R.dimen.config_qsTileStrokeWidthActive);
+        mStrokeWidthInactive = context.getResources()
+                .getDimension(com.android.internal.R.dimen.config_qsTileStrokeWidthInactive);
+        int size = 72 ;
+        addView(mIconFrame, 0);
+        mBg = new ImageView(getContext());
+
+         Drawable  d = context.getResources().getDrawable(R.drawable.qs_tile_background_shape_custom);
+         d.setTintList(ColorStateList.valueOf(Color.TRANSPARENT));
+         int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
+         mBg.setImageDrawable(d);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(bgSize, bgSize, Gravity.CENTER);
+        mIconFrame.addView(mBg, lp);
+        mBg.setLayoutParams(lp);
+        mIcon = icon;
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER);
+        mIconFrame.addView(mIcon, params);
+        mIconFrame.setClipChildren(false);
+        mIconFrame.setClipToPadding(false);
+
+        mTileBackground = newTileBackground();
+        if (mTileBackground instanceof RippleDrawable) {
+            setRipple((RippleDrawable) mTileBackground);
+        }
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        setBackground(mTileBackground);
+                                        
+        mColorActive = context.getColor(R.color.pad_pro_blue);
+        mColorDisabled = Utils.getDisabled(context,
+                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorTertiary));
+        mColorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor);
+        setPadding(0, 0, 0, 0);
+        setClipChildren(false);
+        setClipToPadding(false);
+        mCollapsedView = collapsedView;
+        setFocusable(true);
+    }
+
+ @Override
+    public void setPosition(int position) {
+        // 你的方法实现
+        position = position;
+    }
+
+    public View getBgCircle() {
+        return mBg;
+    }
+
+    protected Drawable newTileBackground() {
+        final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
+        final TypedArray ta = getContext().obtainStyledAttributes(attrs);
+        final Drawable d = ta.getDrawable(0);
+        ta.recycle();
+        return d;
+    }
+
+    private void setRipple(RippleDrawable tileBackground) {
+        mRipple = tileBackground;
+        if (getWidth() != 0) {
+            updateRippleSize();
+        }
+    }
+
+    private void updateRippleSize() {
+        // center the touch feedback on the center of the icon, and dial it down a bit
+        final int cx = mIconFrame.getMeasuredWidth() / 2 + mIconFrame.getLeft();
+        final int cy = mIconFrame.getMeasuredHeight() / 2 + mIconFrame.getTop();
+        final int rad = (int) (mIcon.getHeight() * .85f);
+        mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
+    }
+
+    @Override
+    public void init(QSTile tile) {
+        init(v -> tile.click(this), v -> tile.secondaryClick(this), view -> {
+            tile.longClick(this);
+            return true;
+        });
+    }
+
+    public void init(OnClickListener click, OnClickListener secondaryClick,
+            OnLongClickListener longClick) {
+        setOnClickListener(click);
+        setOnLongClickListener(longClick);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (mRipple != null) {
+            updateRippleSize();
+        }
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        // Avoid layers for this layout - we don't need them.
+        return false;
+    }
+
+    /**
+     * Update the accessibility order for this view.
+     *
+     * @param previousView the view which should be before this one
+     * @return the last view in this view which is accessible
+     */
+    public View updateAccessibilityOrder(View previousView) {
+        setAccessibilityTraversalAfter(previousView.getId());
+        return this;
+    }
+
+    public void onStateChanged(QSTile.State state) {
+        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
+    }
+
+    private void updateStrokeShapeWidth(QSTile.State state) {
+        Resources resources = getContext().getResources();
+        if (!(mBg.getDrawable() instanceof ShapeDrawable)) {
+            return;
+        }
+        ShapeDrawable d = (ShapeDrawable) mBg.getDrawable();
+        d.getPaint().setStyle(Paint.Style.FILL);
+        switch (state.state) {
+            case Tile.STATE_INACTIVE:
+                if (mStrokeWidthInactive >= 0) {
+                    d.getPaint().setStyle(Paint.Style.STROKE);
+                    d.getPaint().setStrokeWidth(mStrokeWidthInactive);
+                }
+                break;
+            case Tile.STATE_ACTIVE:
+                if (mStrokeWidthActive >= 0) {
+                    d.getPaint().setStyle(Paint.Style.STROKE);
+                    d.getPaint().setStrokeWidth(mStrokeWidthActive);
+                }
+                break;
+        }
+    }
+
+    protected void handleStateChanged(QSTile.State state) {
+        updateStrokeShapeWidth(state);
+        int circleColor = getCircleColor(state.state);
+        boolean allowAnimations = animationsEnabled();
+        if (circleColor != mCircleColor) {
+            if (allowAnimations) {
+                ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
+                        .setDuration(QS_ANIM_LENGTH);
+                animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
+                        (Integer) animation.getAnimatedValue())));
+                animator.start();
+            } else {
+                QSIconViewImpl.setTint(mBg, circleColor);
+            }
+            mCircleColor = circleColor;
+        }
+
+        mShowRippleEffect = state.showRippleEffect;
+        setClickable(state.state != Tile.STATE_UNAVAILABLE);
+        setLongClickable(state.handlesLongClick);
+        mIcon.setIcon(state, allowAnimations);
+        setContentDescription(state.contentDescription);
+        final StringBuilder stateDescription = new StringBuilder();
+        switch (state.state) {
+            case Tile.STATE_UNAVAILABLE:
+                stateDescription.append(mContext.getString(R.string.tile_unavailable));
+                break;
+            case Tile.STATE_INACTIVE:
+                if (state instanceof QSTile.BooleanState) {
+                    stateDescription.append(mContext.getString(R.string.switch_bar_off));
+                }
+                break;
+            case Tile.STATE_ACTIVE:
+                if (state instanceof QSTile.BooleanState) {
+                    stateDescription.append(mContext.getString(R.string.switch_bar_on));
+                }
+                break;
+            default:
+                break;
+        }
+        if (!TextUtils.isEmpty(state.stateDescription)) {
+            stateDescription.append(", ");
+            stateDescription.append(state.stateDescription);
+        }
+        setStateDescription(stateDescription.toString());
+
+        mAccessibilityClass =
+                state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName;
+        if (state instanceof QSTile.BooleanState) {
+            boolean newState = ((BooleanState) state).value;
+            if (mTileState != newState) {
+                mTileState = newState;
+            }
+        }
+    }
+
+    /* The view should not be animated if it's not on screen and no part of it is visible.
+     */
+    protected boolean animationsEnabled() {
+        if (!isShown()) {
+            return false;
+        }
+        if (getAlpha() != 1f) {
+            return false;
+        }
+        getLocationOnScreen(mLocInScreen);
+        return mLocInScreen[1] >= -getHeight();
+    }
+
+    private int getCircleColor(int state) {
+        switch (state) {
+            case Tile.STATE_ACTIVE:
+                return mColorActive;
+            case Tile.STATE_INACTIVE:
+            case Tile.STATE_UNAVAILABLE:
+                return mColorDisabled;
+            default:
+                Log.e(TAG, "Invalid state " + state);
+                return 0;
+        }
+    }
+
+    @Override
+    public void setClickable(boolean clickable) {
+        super.setClickable(clickable);
+        setBackground(clickable && mShowRippleEffect ? mRipple : null);
+    }
+
+    @Override
+    public int getDetailY() {
+        return getTop() + getHeight() / 2;
+    }
+
+    public QSIconView getIcon() {
+        return mIcon;
+    }
+
+    public View getIconWithBackground() {
+        return mIconFrame;
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        if (!TextUtils.isEmpty(mAccessibilityClass)) {
+            event.setClassName(mAccessibilityClass);
+        }
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        // Clear selected state so it is not announce by talkback.
+        info.setSelected(false);
+        if (!TextUtils.isEmpty(mAccessibilityClass)) {
+            info.setClassName(mAccessibilityClass);
+            if (Switch.class.getName().equals(mAccessibilityClass)) {
+                String label = getResources().getString(
+                        mTileState ? R.string.switch_bar_on : R.string.switch_bar_off);
+
+                android.util.Log.d("xsr_mLabel", "label = : "+label);        
+                // Set the text here for tests in
+                // android.platform.test.scenario.sysui.quicksettings. Can be removed when
+                // UiObject2 has a new getStateDescription() API and tests are updated.
+                info.setText(label);
+                info.setChecked(mTileState);
+                info.setCheckable(true);
+                if (isLongClickable()) {
+                    info.addAction(
+                            new AccessibilityNodeInfo.AccessibilityAction(
+                                    AccessibilityNodeInfo.AccessibilityAction
+                                            .ACTION_LONG_CLICK.getId(),
+                                    getResources().getString(
+                                            R.string.accessibility_long_click_tile)));
+                }
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
+        sb.append("locInScreen=(" + mLocInScreen[0] + ", " + mLocInScreen[1] + ")");
+        sb.append(", iconView=" + mIcon.toString());
+        sb.append(", tileState=" + mTileState);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private class H extends Handler {
+        private static final int STATE_CHANGED = 1;
+
+        public H() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == STATE_CHANGED) {
+                handleStateChanged((QSTile.State) msg.obj);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/systemui/qs/tileimpl/QSTileViewBak.java b/src/com/android/systemui/qs/tileimpl/QSTileViewBak.java
new file mode 100644
index 00000000..370932a3
--- /dev/null
+++ b/src/com/android/systemui/qs/tileimpl/QSTileViewBak.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 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.systemui.qs.tileimpl;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.service.quicksettings.Tile;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
+
+import java.util.Objects;
+import com.android.systemui.animation.LaunchableView ;
+import com.android.systemui.animation.LaunchableViewDelegate ;
+import kotlin.Unit;
+
+/** View that represents a standard quick settings tile. **/
+public class QSTileViewBak extends QSTileBaseView implements LaunchableView {
+    private static final int MAX_LABEL_LINES = 2;
+    private static final boolean DUAL_TARGET_ALLOWED = false;
+    private View mDivider;
+    protected TextView mLabel;
+    protected TextView mSecondLine;
+    private ImageView mPadLock;
+    private int mState;
+    private ViewGroup mLabelContainer;
+    private View mExpandIndicator;
+    private View mExpandSpace;
+    private ColorStateList mColorLabelDefault;
+    private ColorStateList mColorLabelUnavailable;
+    LaunchableViewDelegate  launchableViewDelegate ;
+
+    public QSTileViewBak(Context context, QSIconView icon) {
+        this(context, icon, false);
+    }
+
+    public QSTileViewBak(Context context, QSIconView icon, boolean collapsedView) {
+        super(context, icon, collapsedView);
+
+        setClipChildren(false);
+        setClipToPadding(false);
+
+        setClickable(true);
+        setId(View.generateViewId());
+        createLabel();
+        setOrientation(VERTICAL);
+        setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+        mColorLabelDefault = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary);
+        // The text color for unavailable tiles is textColorSecondary, same as secondaryLabel for
+        // contrast purposes
+        mColorLabelUnavailable = Utils.getColorAttr(getContext(),
+                android.R.attr.textColorSecondary);
+    launchableViewDelegate = new LaunchableViewDelegate(
+            this,
+            visibility -> {
+                super.setVisibility(visibility);
+                return Unit.INSTANCE;
+            });
+    }
+
+    public  TextView getLabel() {
+        return mLabel;
+    }
+
+    @Override
+public void setShouldBlockVisibilityChanges(boolean block) {
+    launchableViewDelegate.setShouldBlockVisibilityChanges(block);
+}
+
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
+        FontSizeUtils.updateFontSize(mSecondLine, R.dimen.qs_tile_text_size);
+    }
+
+    @Override
+    public int getDetailY() {
+         android.util.Log.d("xsr_mLabel", "mLabelContainer.getHeight() / 2 = : "+mLabelContainer.getHeight() / 2);
+        return getTop() + mLabelContainer.getTop() + mLabelContainer.getHeight() / 2;
+    }
+
+    protected void createLabel() {
+        mLabelContainer = (ViewGroup) LayoutInflater.from(getContext())
+                .inflate(R.layout.qs_tile_label_bak, this, false);
+     android.util.Log.d("xsr_mLabel", "mLabelContainer = : "+mLabelContainer);
+        mLabelContainer.setClipChildren(false);
+        mLabelContainer.setClipToPadding(false);
+        mLabel = mLabelContainer.findViewById(R.id.tile_label_bak);
+        mPadLock = mLabelContainer.findViewById(R.id.restricted_padlock_bak);
+        mDivider = mLabelContainer.findViewById(R.id.underline_bak);
+        mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator_bak);
+        mExpandSpace = mLabelContainer.findViewById(R.id.expand_space_bak);
+        mSecondLine = mLabelContainer.findViewById(R.id.app_label_bak);
+        addView(mLabelContainer,1);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            android.util.Log.d("xsr_mLabel", "mLabel.getLineCount() = : "+mLabel.getLineCount());
+        // Remeasure view if the primary label requires more then 2 lines or the secondary label
+        // text will be cut off.
+        if (mLabel.getLineCount() > MAX_LABEL_LINES || !TextUtils.isEmpty(mSecondLine.getText())
+                        && mSecondLine.getLineHeight() > mSecondLine.getHeight()) {
+
+           android.util.Log.d("xsr_mLabel", " onMeasure");
+            mLabel.setSingleLine();
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+
+    @Override
+    protected void handleStateChanged(QSTile.State state) {
+        super.handleStateChanged(state);
+        if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) {
+            mLabel.setTextColor(state.state == Tile.STATE_UNAVAILABLE ? mColorLabelUnavailable
+                    : mColorLabelDefault);
+            mState = state.state;
+             android.util.Log.d("xsr_mLabel", "mLabel = : "+state.label);
+            mLabel.setText(state.label);
+        FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
+        FontSizeUtils.updateFontSize(mSecondLine, R.dimen.qs_tile_text_size);
+            mLabel.setVisibility(View.VISIBLE);
+        }
+        // if (!Objects.equals(mSecondLine.getText(), state.secondaryLabel)) {
+        //     mSecondLine.setText(state.secondaryLabel);
+        //     mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE
+        //             : View.VISIBLE);
+        // }
+        boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget;
+        mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+        mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+        mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription
+                : null);
+        if (dualTarget != mLabelContainer.isClickable()) {
+            mLabelContainer.setClickable(dualTarget);
+            mLabelContainer.setLongClickable(dualTarget);
+            mLabelContainer.setBackground(dualTarget ? newTileBackground() : null);
+        }
+        mLabel.setEnabled(!state.disabledByPolicy);
+        mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void init(OnClickListener click, OnClickListener secondaryClick,
+            OnLongClickListener longClick) {
+        super.init(click, secondaryClick, longClick);
+        mLabelContainer.setOnClickListener(secondaryClick);
+        mLabelContainer.setOnLongClickListener(longClick);
+        mLabelContainer.setClickable(false);
+        mLabelContainer.setLongClickable(false);
+    }
+}

有了这两个方法后我们就可以改变形状了,但是烧录发现会闪屏,发现缺少animator,所以我们修改下这个文件

diff --git a/src/com/android/systemui/qs/QSAnimator.java b/src/com/android/systemui/qs/QSAnimator.java
index 8b451c19..439bb9e0 100644
--- a/src/com/android/systemui/qs/QSAnimator.java
+++ b/src/com/android/systemui/qs/QSAnimator.java
@@ -358,44 +358,44 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener
                     );
 
                     // Label containers
-                    translateContent(
-                            quickTileView.getLabelContainer(),
-                            tileView.getLabelContainer(),
-                            view,
-                            xOffset,
-                            yOffset,
-                            mTmpLoc1,
-                            translationXBuilder,
-                            translationYBuilder,
-                            qqsTranslationYBuilder
-                    );
+                    // translateContent(
+                    //         quickTileView.getLabelContainer(),
+                    //         tileView.getLabelContainer(),
+                    //         view,
+                    //         xOffset,
+                    //         yOffset,
+                    //         mTmpLoc1,
+                    //         translationXBuilder,
+                    //         translationYBuilder,
+                    //         qqsTranslationYBuilder
+                    // );
 
                     // Secondary icon
-                    translateContent(
-                            quickTileView.getSecondaryIcon(),
-                            tileView.getSecondaryIcon(),
-                            view,
-                            xOffset,
-                            yOffset,
-                            mTmpLoc1,
-                            translationXBuilder,
-                            translationYBuilder,
-                            qqsTranslationYBuilder
-                    );
+                    // translateContent(
+                    //         quickTileView.getSecondaryIcon(),
+                    //         tileView.getSecondaryIcon(),
+                    //         view,
+                    //         xOffset,
+                    //         yOffset,
+                    //         mTmpLoc1,
+                    //         translationXBuilder,
+                    //         translationYBuilder,
+                    //         qqsTranslationYBuilder
+                    // );
 
                     // Secondary labels on tiles not in QQS have two alpha animation applied:
                     // * on the tile themselves
                     // * on TileLayout
                     // Therefore, we use a quadratic interpolator animator to animate the alpha
                     // for tiles in QQS to match.
-                    quadraticInterpolatorBuilder
-                            .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1);
-                    nonFirstPageAlphaBuilder
-                            .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0);
+                    //quadraticInterpolatorBuilder
+                       //     .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1);
+                    //nonFirstPageAlphaBuilder
+                        //    .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0);
 
                     mAnimatedQsViews.add(tileView);
                     mAllViews.add(quickTileView);
-                    mAllViews.add(quickTileView.getSecondaryLabel());
+                  //  mAllViews.add(quickTileView.getSecondaryLabel());
                 } else if (!isIconInAnimatedRow(count)) {
                     // Pretend there's a corresponding QQS tile (for the position) that we are
                     // expanding from.
@@ -414,8 +414,8 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener
                     mOtherFirstPageTilesHeightAnimator.addView(tileView);
                     tileView.setClipChildren(true);
                     tileView.setClipToPadding(true);
-                    firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
-                    mAllViews.add(tileView.getSecondaryLabel());
+                 //   firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
+                  //  mAllViews.add(tileView.getSecondaryLabel());
                 }
 
                 mAllViews.add(tileView);

这里我们就修改了形状了,需要注意的是android14需要给自己写的 QSTileViewBak实现LaunchableView这个类,不然点击以太网这个tile会闪退。

2.添加新的tile

之后我们就需要实现对应的tile了,这里需要新增三个tile,首页,屏幕保护和设置,直接放补丁了,都简单

diff --git a/src/com/android/systemui/qs/tiles/BackHomeTile.java b/src/com/android/systemui/qs/tiles/BackHomeTile.java
new file mode 100644
index 00000000..82321faa
--- /dev/null
+++ b/src/com/android/systemui/qs/tiles/BackHomeTile.java
@@ -0,0 +1,114 @@
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.widget.Switch;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.R;
+import javax.inject.Inject;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
+import android.sysprop.TelephonyProperties;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.util.Log;
+import android.widget.Switch;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+
+/** Quick settings tile: Return to Home **/
+public class BackHomeTile extends QSTileImpl {
+    public static final String TILE_SPEC = "home";
+    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_home);
+    private ActivityStarter mActivityStarter;
+
+    @Inject
+    public BackHomeTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
+            @Background Looper backgroundLooper,
+            @Main Handler mainHandler,
+            FalsingManager falsingManager,
+            MetricsLogger metricsLogger,
+            StatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter,
+            QSLogger qsLogger
+
+    ) {
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+                statusBarStateController, activityStarter, qsLogger);
+         mActivityStarter=activityStarter;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    // 处理点击事件
+    @Override
+    public void handleClick(@Nullable View view) {
+       // 假设mActivityStarter是ActivityStarter的实例,已经通过依赖注入提供
+      Intent intent = new Intent(Intent.ACTION_MAIN);
+      intent.addCategory(Intent.CATEGORY_HOME);
+      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+    // 使用ActivityStarter启动主屏幕
+     mActivityStarter.startActivity(intent, false );
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        state.icon = mIcon;
+        state.label = mContext.getString(R.string.quick_settings_home_label);
+        state.contentDescription = state.label;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return 0;
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_home_label);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/systemui/qs/tiles/InternetTile.java b/src/com/android/systemui/qs/tiles/InternetTile.java
index 05351a53..88ade77c 100644
--- a/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -132,6 +132,7 @@ public class InternetTile extends QSTileImpl {
 
     @Override
     protected void handleClick(@Nullable View view) {
+         android.util.Log.d("xsr_wifi", "view: "+view);
         mHandler.post(() -> mInternetDialogFactory.create(true,
                 mAccessPointController.canConfigMobileData(),
                 mAccessPointController.canConfigWifi(), view));
diff --git a/src/com/android/systemui/qs/tiles/ScreenSaverTile.java b/src/com/android/systemui/qs/tiles/ScreenSaverTile.java
new file mode 100644
index 00000000..55d7bf8f
--- /dev/null
+++ b/src/com/android/systemui/qs/tiles/ScreenSaverTile.java
@@ -0,0 +1,136 @@
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.provider.Settings;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.R;
+import javax.inject.Inject;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.ThreadFactory;
+
+import android.content.Intent;
+import android.provider.Settings;
+import android.service.quicksettings.Tile;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.R;
+import javax.inject.Inject;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
+import android.sysprop.TelephonyProperties;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.util.Log;
+import android.widget.Switch;
+import com.android.settingslib.dream.DreamBackend;
+
+import androidx.annotation.Nullable;
+
+/** Quick settings tile: Screen Saver **/
+public class ScreenSaverTile extends QSTileImpl {
+    public static final String TILE_SPEC = "screen_saver";
+    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_screen_saver); // Replace with your icon drawable
+    private ActivityStarter mActivityStarter;
+    private Handler mHandler;
+
+    @Inject
+    public ScreenSaverTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
+            @Background Looper backgroundLooper,
+            @Main Handler mainHandler,
+            FalsingManager falsingManager,
+            MetricsLogger metricsLogger,
+            StatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter,
+            QSLogger qsLogger
+    ) {
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+                statusBarStateController, activityStarter, qsLogger);
+        mActivityStarter = activityStarter;
+        mHandler = mainHandler;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    // 处理点击事件
+    @Override
+    protected void handleClick(@Nullable View view) {
+        startDreaming();
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        state.icon = mIcon;
+        state.label = mContext.getString(R.string.quick_settings_screen_saver_label); // Replace with your label
+        state.contentDescription = state.label;
+    }
+
+    private void startDreaming() {
+    // 获取 DreamBackend 的实例
+    DreamBackend dreamBackend = DreamBackend.getInstance(mContext);
+
+    // 检查屏幕保护程序是否可用
+    if (dreamBackend.isEnabled()) {
+        // 启动屏幕保护程序
+        dreamBackend.startDreaming();
+     } 
+//else {
+//         // 如果屏幕保护程序不可用,可以显示一个提示或者错误信息
+//         Toast.makeText(this, "Screen saver is not enabled.", Toast.LENGTH_SHORT).show();
+//     }
+}
+
+    @Override
+    public int getMetricsCategory() {
+        return 0;
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_screen_saver_label); // Replace with your label
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/systemui/qs/tiles/SettingsTile.java b/src/com/android/systemui/qs/tiles/SettingsTile.java
new file mode 100644
index 00000000..54d8198e
--- /dev/null
+++ b/src/com/android/systemui/qs/tiles/SettingsTile.java
@@ -0,0 +1,120 @@
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.provider.Settings;
+import android.service.quicksettings.Tile;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.R;
+import javax.inject.Inject;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
+import android.sysprop.TelephonyProperties;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.util.Log;
+import android.widget.Switch;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+
+/** Quick settings tile: Open Settings **/
+public class SettingsTile extends QSTileImpl {
+    public static final String TILE_SPEC = "settings";
+    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_settings);
+     private ActivityStarter mActivityStarter;
+     private Handler handler;
+
+    @Inject
+    public SettingsTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
+            @Background Looper backgroundLooper,
+            @Main Handler mainHandler,
+            FalsingManager falsingManager,
+            MetricsLogger metricsLogger,
+            StatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter,
+            QSLogger qsLogger
+
+    ) {
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+                statusBarStateController, activityStarter, qsLogger);
+                mActivityStarter = activityStarter;
+                handler = mainHandler;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    // 处理点击事件
+    @Override
+    protected  void handleClick(@Nullable View view) {
+    handler.post(new Runnable() {
+    @Override
+    public void run() {
+                  Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mActivityStarter.startActivity(intent, /* dismissShade= */ true);
+        // 在这里执行需要在主线程上的操作
+    }
+});
+  
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        state.icon = mIcon;
+        state.label = mContext.getString(R.string.quick_settings_settings_label);
+        state.contentDescription = state.label;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return 0;
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_settings_label);
+    }
+}
\ No newline at end of file

添加这些后需要注意 没有写资源文件,大家可以自行编辑,文章最后会附上全部patch

diff --git a/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 92aa9866..82f462e8 100644
--- a/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -24,6 +24,9 @@ import com.android.systemui.qs.tiles.DataSaverTile
 import com.android.systemui.qs.tiles.HotspotTile
 import com.android.systemui.qs.tiles.InternetTile
 import com.android.systemui.qs.tiles.NfcTile
+import com.android.systemui.qs.tiles.SettingsTile
+import com.android.systemui.qs.tiles.BackHomeTile
+import com.android.systemui.qs.tiles.ScreenSaverTile
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoMap
@@ -68,6 +71,21 @@ interface ConnectivityModule {
     @StringKey(DataSaverTile.TILE_SPEC)
     fun bindDataSaverTile(dataSaverTile: DataSaverTile): QSTileImpl<*>
 
+    @Binds
+    @IntoMap
+    @StringKey(BackHomeTile.TILE_SPEC)
+    fun bindBackHomeTile(backHomeTile: BackHomeTile): QSTileImpl<*>
+
+    @Binds
+    @IntoMap
+    @StringKey(SettingsTile.TILE_SPEC)
+    fun bindSettingsTile(settingsTile: SettingsTile): QSTileImpl<*>
+
+    @Binds
+    @IntoMap
+    @StringKey(ScreenSaverTile.TILE_SPEC)
+    fun bindScreenSaverTile(screenSaverTile: ScreenSaverTile): QSTileImpl<*>
+
     /** Inject NfcTile into tileMap in QSModule */
     @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*>
 }

此外还需要注意要在config.xml里修改不然没办法显示

diff --git a/res/values/config.xml b/res/values/config.xml
index d608f7bf..7c284ede 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -78,12 +78,11 @@
 
     
     
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService),custom(com.google.android.gms/.nearby.sharing.SharingTileService),qr_code_scanner
+      home,screen_saver,mictoggle,cameratoggle,internet,bt,settings
     
 
     
     
-        com.android.permissioncontroller.permission.service.v33.SafetyCenterQsTileService
     
 
     
@@ -91,7 +90,7 @@
 
     
     
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling
+        home,settings,screen_saver,internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling
     
 
     

但是我修改这个后他依然没有办法正确显示,之后我就

diff --git a/src/com/android/systemui/qs/QSTileHost.java b/src/com/android/systemui/qs/QSTileHost.java
index 57bb203b..cd80ba7a 100644
--- a/src/com/android/systemui/qs/QSTileHost.java
+++ b/src/com/android/systemui/qs/QSTileHost.java
@@ -287,6 +287,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, P
         if (!TILES_SETTING.equals(key)) {
             return;
         }
+
+       if(newValue == null){
+              
+            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_default);
+         }
         // Do not process tiles if the flag is enabled.
         if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
             return;

这样就可以显示自己想要的了,只需要修改默认值就可以啦,但是烧录玩发现经常多一个,就是系统会自己添加,我们只需要

diff --git a/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index f6d53b3b..1c3a7fa5 100644
--- a/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -361,11 +361,11 @@ public class AutoTileManager implements UserAwareController {
     }
 
     private void initSafetyTile() {
-        if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
+       // if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
             return;
-        }
-        mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
-        mAutoTracker.setTileAdded(mSafetySpec);
+      //  }
+       // mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
+        //mAutoTracker.setTileAdded(mSafetySpec);
     }
 
     @VisibleForTesting

但是其实给config里面的safety_quick_settings_tile_class改成null就可以,为了保险我改了两处

到了这里tile的修改就基本结束了,

之后我们就要修改布局了

3.修改布局

我们要实现那个效果,首先要将page现在在第一行,再将亮度调节和音量调节显示

diff --git a/src/com/android/systemui/qs/QSPanel.java b/src/com/android/systemui/qs/QSPanel.java
index b936c41d..fe411012 100644
--- a/src/com/android/systemui/qs/QSPanel.java
+++ b/src/com/android/systemui/qs/QSPanel.java
@@ -49,6 +49,12 @@ import com.android.systemui.tuner.TunerService.Tunable;
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.constraintlayout.widget.ConstraintSet;
+import android.graphics.drawable.Drawable;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import com.android.systemui.settings.volume.VolumeSliderView;
+
 /** View that represents the quick settings tile panel (when expanded/pulled down). **/
 public class QSPanel extends LinearLayout implements Tunable {
 
@@ -104,6 +110,7 @@ public class QSPanel extends LinearLayout implements Tunable {
     private ViewGroup mMediaHostView;
     private boolean mShouldMoveMediaOnExpansion = true;
     private QSLogger mQsLogger;
+    View volumeSliderView ;
     /**
      * Specifies if we can collapse to QQS in current state. In split shade that should be always
      * false. It influences available accessibility actions.
@@ -128,7 +135,10 @@ public class QSPanel extends LinearLayout implements Tunable {
     void initialize(QSLogger qsLogger) {
         mQsLogger = qsLogger;
         mTileLayout = getOrCreateTileLayout();
+        mTileLayout.setCowsAndColumns(7,1);
 
+     volumeSliderView =   LayoutInflater.from(mContext).inflate(
+                 R.layout.quick_settings_volume_dialog, this, false);
         if (mUsingMediaPlayer) {
             mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
             mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
@@ -182,7 +192,7 @@ public class QSPanel extends LinearLayout implements Tunable {
             removeView(mBrightnessView);
             mMovableContentStartIndex--;
         }
-        addView(view, 0);
+      //  addView(view, 0);
         mBrightnessView = view;
 
         setBrightnessViewMargin();
@@ -398,7 +408,7 @@ public class QSPanel extends LinearLayout implements Tunable {
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mFooter = findViewById(R.id.qs_footer);
+       // mFooter = findViewById(R.id.qs_footer);
     }
 
     private void updateHorizontalLinearLayoutMargins() {
@@ -430,18 +440,18 @@ public class QSPanel extends LinearLayout implements Tunable {
         return true;
     }
 
-    private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) {
-        int index = parent == this ? mMovableContentStartIndex : 0;
+    private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout , int index) {
+      //  int index = parent == this ? mMovableContentStartIndex : 0;
 
         // Let's first move the tileLayout to the new parent, since that should come first.
         switchToParent((View) newLayout, parent, index);
-        index++;
+      //  index++;
 
-        if (mFooter != null) {
-            // Then the footer with the settings
-            switchToParent(mFooter, parent, index);
-            index++;
-        }
+        // if (mFooter != null) {
+        //     // Then the footer with the settings
+        //     switchToParent(mFooter, parent, index);
+        //     index++;
+        // }
     }
 
     private void switchToParent(View child, ViewGroup parent, int index) {
@@ -607,7 +617,65 @@ public class QSPanel extends LinearLayout implements Tunable {
             Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force);
             mUsingHorizontalLayout = horizontal;
             ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
-            switchAllContentToParent(newParent, mTileLayout);
+if(mBrightnessView != null){
+         Log.w("xsr_edit","mBrightnessView 22222222222222222222222 ="+mBrightnessView);
+       
+           //switchToParent(mBrightnessView,newParent,2);
+ConstraintLayout constraintLayout = new ConstraintLayout(mContext);
+constraintLayout.setLayoutParams(new ConstraintLayout.LayoutParams(
+        ConstraintLayout.LayoutParams.MATCH_PARENT, 84));
+
+
+
+// 为mBrightnessView设置参数
+
+// 添加mBrightnessView到ConstraintLayout
+//yconstraintLayout.addView(mBrightnessView);
+        ViewGroup currentParent = (ViewGroup) mBrightnessView.getParent();
+        if (currentParent != constraintLayout) {
+            if (currentParent != null) {
+                currentParent.removeView(mBrightnessView);
+            }
+        }
+           ViewGroup currentParents = (ViewGroup) volumeSliderView.getParent();
+        if (currentParents != constraintLayout) {
+            if (currentParents != null) {
+                currentParents.removeView(volumeSliderView);
+            }
+        }
+
+    //   // 将mBrightnessView添加到LinearLayout
+    
+       
+       constraintLayout.addView(mBrightnessView,0);
+       constraintLayout.addView(volumeSliderView ,1);
+
+          ConstraintSet constraintSet = new ConstraintSet();
+   constraintSet.clone(constraintLayout)  ;
+   constraintSet.connect(mBrightnessView.getId(), ConstraintSet.START,
+           ConstraintSet.PARENT_ID, ConstraintSet.START, 0); // 替换 yourStartMargin 为实际值
+   constraintSet.connect(mBrightnessView.getId(), ConstraintSet.END,
+           volumeSliderView.getId(), ConstraintSet.START, 24)  ;
+   constraintSet.connect(volumeSliderView.getId(), ConstraintSet.START,
+           mBrightnessView.getId(), ConstraintSet.END, 24);
+   constraintSet.connect(volumeSliderView.getId(), ConstraintSet.END,
+           ConstraintSet.PARENT_ID, ConstraintSet.END, 0); // 替换 yourEndMargin 为实际  
+   constraintSet.applyTo(constraintLayout) ;
+
+
+        
+    //  调用switchToParent方法,传入新的LinearLayout作为父布局
+    switchAllContentToParent(newParent, mTileLayout,0); 
+      switchToParent(constraintLayout, newParent, 1);
+               
+
+         }
+         else {
+             Log.w("xsr_edit","else");
+            // switchAllContentToParent(newParent,mRegularTileLayout,1);
+             switchAllContentToParent(newParent, mTileLayout,0); 
+         }
+           // switchAllContentToParent(newParent, mTileLayout);
             reAttachMediaHost(mediaHostView, horizontal);
             if (needsDynamicRowsAndColumns()) {
                 mTileLayout.setMinRows(horizontal ? 2 : 1);
@@ -684,6 +752,8 @@ public class QSPanel extends LinearLayout implements Tunable {
         /** */
         int getOffsetTop(QSPanelControllerBase.TileRecord tile);
 
+        void setCowsAndColumns(int rows, int columns)  ;  
+
         /** */
         boolean updateResources();
4.添加音量调节的逻辑控制
diff --git a/src/com/android/systemui/settings/volume/VolumeSliderView.java b/src/com/android/systemui/settings/volume/VolumeSliderView.java
new file mode 100644
index 00000000..18805b54
--- /dev/null
+++ b/src/com/android/systemui/settings/volume/VolumeSliderView.java
@@ -0,0 +1,151 @@
+package com.android.systemui.settings.volume;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.SeekBar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+import android.media.AudioManager;
+
+/**
+ * {@code FrameLayout} used to show and manipulate a {@link SeekBar} for volume control.
+ */
+public class VolumeSliderView extends FrameLayout implements VolumeChangeObserver.VolumeChangeListener {
+
+    @NonNull
+    private SeekBar mSeekBar;
+    private DispatchTouchEventListener mListener;
+    private AudioManager mAudioManager;
+     private VolumeChangeObserver mVolumeChangeObserver;
+
+    public VolumeSliderView(Context context) {
+        this(context, null);
+       
+    }
+
+    public VolumeSliderView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initAudioManager(context);
+            mVolumeChangeObserver = new VolumeChangeObserver(context);
+        mVolumeChangeObserver.setVolumeChangeListener(this);
+    }
+
+    private void initAudioManager(Context context) {
+    mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    
+}
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mSeekBar = findViewById(R.id.v_slider);
+        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                // Handle volume change
+            if (fromUser) {
+            int volume = progress;
+            // 确保音量值在有效范围内
+            volume = Math.max(0, Math.min(volume, mSeekBar.getMax()));
+            // 设置音量
+            setVolume(volume);
+        }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                // Handle start tracking touch
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                // Handle stop tracking touch
+            }
+        });
+        mVolumeChangeObserver.registerReceiver();
+        setValue(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+    }
+
+    /**
+     * Attaches a listener to relay touch events.
+     * @param listener use {@code null} to remove listener
+     */
+    public void setOnDispatchTouchEventListener(DispatchTouchEventListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (mListener != null) {
+            mListener.onDispatchTouchEvent(ev);
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    private void setVolume(int volume) {
+    if (mAudioManager != null) {
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        android.util.Log.d("xsr_volume", "maxVolume: "+maxVolume);
+        int normalizedVolume = (int) (volume * (float) maxVolume / mSeekBar.getMax());
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, normalizedVolume, 0);
+    }
+}
+
+    /**
+     * Sets the maximum value of the {@link SeekBar}.
+     * @param max
+     */
+    public void setMax(int max) {
+        mSeekBar.setMax(max);
+    }
+
+    /**
+     * Sets the current value of the {@link SeekBar}.
+     * @param value
+     */
+    public void setValue(int value) {
+                android.util.Log.d("xsr_volume", "onVolumeChanged: "+value);
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        android.util.Log.d("xsr_volume", "maxVolume: "+maxVolume);
+         android.util.Log.d("xsr_volume", "mSeekBar.getMax(): "+mSeekBar.getMax());
+        mSeekBar.setProgress((int)(value /(float) maxVolume * mSeekBar.getMax()));
+    }
+
+    /**
+     * @return the current value of the {@link SeekBar}
+     */
+    public int getValue() {
+        return mSeekBar.getProgress();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // Additional layout logic if necessary
+    }
+
+    /**
+     * Interface to attach a listener for {@link View#dispatchTouchEvent}.
+     */
+    @FunctionalInterface
+    interface DispatchTouchEventListener {
+        boolean onDispatchTouchEvent(MotionEvent ev);
+    }
+
+
+    @Override
+    public void onVolumeChanged(int volume) {
+        //系统媒体音量改变时的回调
+       // android.util.Log.d("xsr_volume", "onVolumeChanged: "+volume);
+     //   int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+       setValue(volume );
+    }
+}
\ No newline at end of file

现在就可以控制音量了,但是发现其他地方调节后这里的无法同步,我们新增一个

diff --git a/src/com/android/systemui/settings/volume/VolumeChangeObserver.java b/src/com/android/systemui/settings/volume/VolumeChangeObserver.java
new file mode 100644
index 00000000..fda60d01
--- /dev/null
+++ b/src/com/android/systemui/settings/volume/VolumeChangeObserver.java
@@ -0,0 +1,117 @@
+package com.android.systemui.settings.volume;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+
+/**
+ * This class is used to observe changes in the system's media volume.
+ */
+public class VolumeChangeObserver {
+
+    private static final String VOLUME_CHANGED_ACTION = AudioManager.VOLUME_CHANGED_ACTION;
+    private static final String EXTRA_VOLUME_STREAM_TYPE = AudioManager.EXTRA_VOLUME_STREAM_TYPE;
+    private static final String EXTRA_VOLUME_STREAM_VALUE = AudioManager.EXTRA_VOLUME_STREAM_VALUE;
+
+    public interface VolumeChangeListener {
+        /**
+         * Called when the system media volume changes.
+         *
+         * @param volume The new volume level.
+         */
+        void onVolumeChanged(int volume);
+    }
+
+    private VolumeChangeListener mVolumeChangeListener;
+    private VolumeBroadcastReceiver mVolumeBroadcastReceiver;
+    private Context mContext;
+    private AudioManager mAudioManager;
+    private boolean mRegistered = false;
+
+    public VolumeChangeObserver(Context context) {
+        mContext = context.getApplicationContext(); // Use application context to avoid leaks
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    /**
+     * Gets the current media volume.
+     *
+     * @return The current media volume, or -1 if the AudioManager is null.
+     */
+    public int getCurrentMusicVolume() {
+        return mAudioManager != null ? mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC) : -1;
+    }
+
+    /**
+     * Gets the maximum media volume.
+     *
+     * @return The maximum media volume, or a default value if the AudioManager is null.
+     */
+    public int getMaxMusicVolume() {
+        return mAudioManager != null ? mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) : 15;
+    }
+
+    /**
+     * Registers the volume broadcast receiver.
+     */
+    public void registerReceiver() {
+        if (!mRegistered) {
+            mVolumeBroadcastReceiver = new VolumeBroadcastReceiver(this);
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(VOLUME_CHANGED_ACTION);
+            mContext.registerReceiver(mVolumeBroadcastReceiver, filter);
+            mRegistered = true;
+        }
+    }
+
+        public VolumeChangeListener getVolumeChangeListener() {
+        return mVolumeChangeListener;
+    }
+
+    public void setVolumeChangeListener(VolumeChangeListener volumeChangeListener) {
+        this.mVolumeChangeListener = volumeChangeListener;
+    }
+
+    /**
+     * Unregisters the volume broadcast receiver.
+     */
+    public void unregisterReceiver() {
+        if (mRegistered) {
+            try {
+                mContext.unregisterReceiver(mVolumeBroadcastReceiver);
+                mVolumeBroadcastReceiver = null;
+                mRegistered = false;
+            } catch (IllegalArgumentException e) {
+                // Handle the case where the receiver is not registered.
+            }
+        }
+    }
+
+    /**
+     * Sets the volume change listener.
+     *
+     * @param volumeChangeListener The listener to be notified of volume changes.
+     */
+    private static class VolumeBroadcastReceiver extends BroadcastReceiver {
+        private final VolumeChangeObserver mVolumeChangeObserver;
+
+        public VolumeBroadcastReceiver(VolumeChangeObserver volumeChangeObserver) {
+            mVolumeChangeObserver = volumeChangeObserver;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (VOLUME_CHANGED_ACTION.equals(intent.getAction())
+                    && intent.hasExtra(EXTRA_VOLUME_STREAM_TYPE)
+                    && intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == AudioManager.STREAM_MUSIC) {
+                int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
+                if (volume >= 0) {
+                    mVolumeChangeObserver.getVolumeChangeListener().onVolumeChanged(volume);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

这样就可以实现基本了

你可能感兴趣的:(android,java)