Style panel with Material designs (4/n)
The following components are styled to follow Material designs in this
modification:
1. Seekbar
- Creating SeekBarWithIconButtonsView for common usage in SystemUI.
The Layout includes a Start Icon and an End Icon that could modify
the progress of the seekbar in the middle.
The Start Icon will be disabled when the progress reaches 0, and
the End Icon will be disabled when the progress reaches Max.
Otherwise, they are enabled
Bug: 257272333
Test: manual - attach videos with the bug
Test: atest SystemUITests:com.android.systemui.common.ui.view.IconDiscreteSliderLinearLayoutTest
Change-Id: If64490a6bdc599c66ae55b8f75e438b11d7c1b54
This commit is contained in:
60
packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
Normal file
60
packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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.
|
||||
-->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/seekbar_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_start"
|
||||
android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
|
||||
android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:adjustViewBounds="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_remove"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:tintMode="src_in" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
style="@android:style/Widget.Material.SeekBar.Discrete"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_end"
|
||||
android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
|
||||
android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:adjustViewBounds="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_add"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:tintMode="src_in" />
|
||||
|
||||
</merge>
|
||||
@@ -15,6 +15,7 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/magnifier_panel_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -145,13 +146,14 @@
|
||||
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
|
||||
android:focusable="true" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/magnifier_zoom_seekbar"
|
||||
android:layout_height="wrap_content"
|
||||
<com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
|
||||
android:id="@+id/magnifier_zoom_slider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="@dimen/magnification_setting_seekbar_margin"
|
||||
android:progress="0"
|
||||
android:max="6" />
|
||||
android:layout_height="wrap_content"
|
||||
app:max="6"
|
||||
app:progress="0"
|
||||
app:iconStartContentDescription="@string/accessibility_control_zoom_out"
|
||||
app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/magnifier_done_button"
|
||||
|
||||
@@ -219,5 +219,12 @@
|
||||
<attr name="biometricsEnrollProgressHelp" format="reference|color" />
|
||||
<attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SeekBarWithIconButtonsView_Layout">
|
||||
<attr name="max" format="integer" />
|
||||
<attr name="progress" format="integer" />
|
||||
<attr name="iconStartContentDescription" format="reference" />
|
||||
<attr name="iconEndContentDescription" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -1134,6 +1134,8 @@
|
||||
<dimen name="magnification_setting_image_button_padding_horizontal">24dp</dimen>
|
||||
<dimen name="magnification_setting_image_button_open_in_full_padding_vertical">16dp</dimen>
|
||||
<dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
|
||||
<dimen name="magnification_setting_seekbar_icon_size">24dp</dimen>
|
||||
|
||||
|
||||
<!-- How far from the right edge of the screen you need to drag the window before the button
|
||||
repositions to the other side. -->
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.widget.Switch;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -79,7 +80,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
|
||||
private final MagnificationGestureDetector mGestureDetector;
|
||||
private boolean mSingleTapDetected = false;
|
||||
|
||||
private SeekBar mZoomSeekbar;
|
||||
private SeekBarWithIconButtonsView mZoomSeekbar;
|
||||
private Switch mAllowDiagonalScrollingSwitch;
|
||||
private LinearLayout mPanelView;
|
||||
private LinearLayout mSettingView;
|
||||
@@ -393,7 +394,8 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
|
||||
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
|
||||
mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
|
||||
|
||||
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
|
||||
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
|
||||
|
||||
mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
|
||||
|
||||
float scale = mSecureSettings.getFloatForUser(
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.ui.view;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* The layout contains a seekbar whose progress could be modified
|
||||
* through the icons on two ends of the seekbar.
|
||||
*/
|
||||
public class SeekBarWithIconButtonsView extends LinearLayout {
|
||||
|
||||
private static final int DEFAULT_SEEKBAR_MAX = 6;
|
||||
private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
|
||||
|
||||
private ImageView mIconStart;
|
||||
private ImageView mIconEnd;
|
||||
private SeekBar mSeekbar;
|
||||
|
||||
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
|
||||
|
||||
public SeekBarWithIconButtonsView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SeekBarWithIconButtonsView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SeekBarWithIconButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public SeekBarWithIconButtonsView(Context context,
|
||||
AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
LayoutInflater.from(context).inflate(
|
||||
R.layout.seekbar_with_icon_buttons, this, /* attachToRoot= */ true);
|
||||
|
||||
mIconStart = findViewById(R.id.icon_start);
|
||||
mIconEnd = findViewById(R.id.icon_end);
|
||||
mSeekbar = findViewById(R.id.seekbar);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.SeekBarWithIconButtonsView_Layout,
|
||||
defStyleAttr, defStyleRes
|
||||
);
|
||||
int max = typedArray.getInt(
|
||||
R.styleable.SeekBarWithIconButtonsView_Layout_max, DEFAULT_SEEKBAR_MAX);
|
||||
int progress = typedArray.getInt(
|
||||
R.styleable.SeekBarWithIconButtonsView_Layout_progress,
|
||||
DEFAULT_SEEKBAR_PROGRESS);
|
||||
mSeekbar.setMax(max);
|
||||
setProgress(progress);
|
||||
|
||||
int iconStartContentDescriptionId = typedArray.getResourceId(
|
||||
R.styleable.SeekBarWithIconButtonsView_Layout_iconStartContentDescription,
|
||||
/* defValue= */ 0);
|
||||
int iconEndContentDescriptionId = typedArray.getResourceId(
|
||||
R.styleable.SeekBarWithIconButtonsView_Layout_iconEndContentDescription,
|
||||
/* defValue= */ 0);
|
||||
if (iconStartContentDescriptionId != 0) {
|
||||
final String contentDescription =
|
||||
context.getString(iconStartContentDescriptionId);
|
||||
mIconStart.setContentDescription(contentDescription);
|
||||
}
|
||||
if (iconEndContentDescriptionId != 0) {
|
||||
final String contentDescription =
|
||||
context.getString(iconEndContentDescriptionId);
|
||||
mIconEnd.setContentDescription(contentDescription);
|
||||
}
|
||||
|
||||
typedArray.recycle();
|
||||
} else {
|
||||
mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
|
||||
setProgress(DEFAULT_SEEKBAR_PROGRESS);
|
||||
}
|
||||
|
||||
mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
|
||||
|
||||
mIconStart.setOnClickListener((view) -> {
|
||||
final int progress = mSeekbar.getProgress();
|
||||
if (progress > 0) {
|
||||
mSeekbar.setProgress(progress - 1);
|
||||
setIconViewEnabled(mIconStart, mSeekbar.getProgress() > 0);
|
||||
}
|
||||
});
|
||||
|
||||
mIconEnd.setOnClickListener((view) -> {
|
||||
final int progress = mSeekbar.getProgress();
|
||||
if (progress < mSeekbar.getMax()) {
|
||||
mSeekbar.setProgress(progress + 1);
|
||||
setIconViewEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void setIconViewEnabled(View iconView, boolean enabled) {
|
||||
iconView.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a onSeekbarChangeListener to the seekbar in the layout.
|
||||
* We update the Start Icon and End Icon if needed when the seekbar progress is changed.
|
||||
*/
|
||||
public void setOnSeekBarChangeListener(
|
||||
@Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
|
||||
mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start and End icons might need to be updated when there is a change in seekbar progress.
|
||||
* Icon Start will need to be enabled when the seekbar progress is larger than 0.
|
||||
* Icon End will need to be enabled when the seekbar progress is less than Max.
|
||||
*/
|
||||
private void updateIconViewIfNeeded(int progress) {
|
||||
setIconViewEnabled(mIconStart, progress > 0);
|
||||
setIconViewEnabled(mIconEnd, progress < mSeekbar.getMax());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets progress to the seekbar in the layout.
|
||||
*/
|
||||
public void setProgress(int progress) {
|
||||
mSeekbar.setProgress(progress);
|
||||
updateIconViewIfNeeded(progress);
|
||||
}
|
||||
|
||||
private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
|
||||
private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (mOnSeekBarChangeListener != null) {
|
||||
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
|
||||
}
|
||||
updateIconViewIfNeeded(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
if (mOnSeekBarChangeListener != null) {
|
||||
mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
if (mOnSeekBarChangeListener != null) {
|
||||
mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
|
||||
}
|
||||
}
|
||||
|
||||
void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
|
||||
mOnSeekBarChangeListener = listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.ui.view;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Tests for {@link SeekBarWithIconButtonsView}
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
|
||||
|
||||
private ImageView mIconStart;
|
||||
private ImageView mIconEnd;
|
||||
private SeekBar mSeekbar;
|
||||
private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
|
||||
mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
|
||||
mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
|
||||
mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSeekBarProgressZero_startIconAndFrameDisabled() {
|
||||
mIconDiscreteSliderLinearLayout.setProgress(0);
|
||||
|
||||
assertThat(mIconStart.isEnabled()).isFalse();
|
||||
assertThat(mIconEnd.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSeekBarProgressMax_endIconAndFrameDisabled() {
|
||||
mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax());
|
||||
|
||||
assertThat(mIconEnd.isEnabled()).isFalse();
|
||||
assertThat(mIconStart.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSeekBarProgressMax_allIconsAndFramesEnabled() {
|
||||
// We are using the default value for the max of seekbar.
|
||||
// Therefore, the max value will be DEFAULT_SEEKBAR_MAX = 6.
|
||||
mIconDiscreteSliderLinearLayout.setProgress(1);
|
||||
|
||||
assertThat(mIconStart.isEnabled()).isTrue();
|
||||
assertThat(mIconEnd.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
|
||||
mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
|
||||
mIconEnd.performClick();
|
||||
|
||||
assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickIconStart_currentProgressIsOne_reachesZero() {
|
||||
mIconDiscreteSliderLinearLayout.setProgress(1);
|
||||
mIconStart.performClick();
|
||||
|
||||
assertThat(mSeekbar.getProgress()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user