0%

View

自定义View

组合控件

https://www.jianshu.com/p/8b0c145acef2

在 Android 中,通过组合控件的方式自定义 View,可以将多个基础控件组合在一起形成一个新的控件。这种方法可以用于封装常见的 UI 组件,以提高代码的可重用性和可维护性。以下是一个详细的示例,演示如何创建一个组合控件,包括一个图片、一个标题和一个描述文本。

创建自定义组合控件类

首先,创建一个自定义的 View 类,继承自 LinearLayout,并在其中组合多个基础控件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class CustomView extends LinearLayout {

private ImageView imageView;
private TextView titleTextView;
private TextView descriptionTextView;

public CustomView(Context context) {
super(context);
init(context, null);
}

public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.view_custom, this, true);

imageView = findViewById(R.id.imageView);
titleTextView = findViewById(R.id.titleTextView);
descriptionTextView = findViewById(R.id.descriptionTextView);

if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);

try {
int imageResId = a.getResourceId(R.styleable.CustomView_imageSrc, 0);
String titleText = a.getString(R.styleable.CustomView_titleText);
String descriptionText = a.getString(R.styleable.CustomView_descriptionText);

if (imageResId != 0) {
imageView.setImageResource(imageResId);
}
titleTextView.setText(titleText);
descriptionTextView.setText(descriptionText);
} finally {
a.recycle();
}
}
}

public void setImageResource(int resId) {
imageView.setImageResource(resId);
}

public void setTitleText(String text) {
titleTextView.setText(text);
}

public void setDescriptionText(String text) {
descriptionTextView.setText(text);
}
}

创建布局文件 (view_custom.xml)

创建一个布局文件,定义组合控件的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_foreground"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"/>

<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textSize="18sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="4dp"/>

<TextView
android:id="@+id/descriptionTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description"
android:textSize="14sp"
android:layout_gravity="center_horizontal"/>
</merge>

定义自定义属性

res/values 文件夹中创建一个 attrs.xml 文件,定义自定义属性。

1
2
3
4
5
6
7
<resources>
<declare-styleable name="CustomView">
<attr name="imageSrc" format="reference"/>
<attr name="titleText" format="string"/>
<attr name="descriptionText" format="string"/>
</declare-styleable>
</resources>

使用自定义组合控件

在你的布局文件中使用自定义的组合控件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">

<com.example.yourapp.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:imageSrc="@drawable/ic_launcher_foreground"
app:titleText="Custom Title"
app:descriptionText="This is a description." />

</LinearLayout>

解释

  1. CustomView.java

    • 定义自定义控件类 CustomView,继承自 LinearLayout
    • 使用 LayoutInflater 将布局文件 view_custom.xml 加载到当前控件中。
    • 通过 TypedArray 获取自定义属性值并应用到控件上。
    • 提供方法来动态设置图片、标题和描述文本。
  2. view_custom.xml

    • 使用 merge 标签将组合控件的布局定义在一个独立的 XML 文件中。
  3. attrs.xml

    • 定义自定义属性 imageSrctitleTextdescriptionText
  4. activity_main.xml

    • 在布局文件中使用自定义的组合控件,并通过自定义属性设置图片、标题和描述文本。

通过这种方式,你可以轻松地创建和使用自定义的组合控件,提高 UI 组件的可重用性和可维护性。

自定义 View

https://blog.csdn.net/nihaomabmt/article/details/109668801

基本方法:继承已有的组件,重写方法 onDraw(), onMeasure(), onKeyDown()
进阶:继承 View, 在构造方法中获取 XML 中和动态设置的属性和参数,重写 onMeasure()onDraw() 方法

在 XML 中添加属性:

  • Define custom attributes for your view in a resource element
    在 res/values/attrs.xml 中添加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <resources>
    <declare-styleable name="PieChart">
    <attr name="showText" format="boolean" />
    <attr name="labelPosition" format="enum">
    <enum name="left" value="0"/>
    <enum name="right" value="1"/>
    </attr>
    </declare-styleable>
    </resources>
    自定义属性命名空间:http://schemas.android.com/apk/res/[your package name]
    非自定义属性命名空间:http://schemas.android.com/apk/res/android
    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto">
    <com.example.customviews.charting.PieChart
    custom:showText="true"
    custom:labelPosition="left" />
    </LinearLayout>
  • Specify values for the attributes in your XML layout
  • Retrieve attribute values at runtime
  • Apply the retrieved attribute values to your view
    pass the AttributeSet to obtainStyledAttributes(). This method passes back a TypedArray array of values that have already been dereferenced and styled.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public PieChart(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray a = context.getTheme().obtainStyledAttributes(
    attrs,
    R.styleable.PieChart,
    0, 0);

    try {
    mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
    textPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
    } finally {
    a.recycle(); // TypedArray 使用完要回收
    }
    }

在代码中动态添加属性:

1
2
3
4
5
6
7
8
9
public boolean isShowText() {
return mShowText;
}

public void setShowText(boolean showText) {
mShowText = showText;
invalidate(); // redrawn, invalidate the view after any change to its properties that might change its appearance
requestLayout(); // you need to request a new layout if a property changes that might affect the size or shape of the view
}

重写 onDraw 方法

利用 Canvas 和 Paint 对象进行绘制,Canvas 决定画什么形状,Paint 决定形状的颜色等属性。在构造方法中做这些属性的初始画,不要在 onDraw() 方法中做。

在代码中动态设置 View 宽高

1
2
3
4
5
myGraphView.setLayoutParams(new LayoutParams(width, height));

ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = 130;
view.setLayoutParams(params);

设置全屏

参考: https://developer.android.com/training/system-ui/immersive?hl=zh-cn#EnableFullscreen

Android 提供了三个用于将应用设为全屏模式选项:向后倾斜模式、沉浸模式和粘性沉浸模式。在所有三种方法中,系统栏都是隐藏的,您的 Activity 会持续收到所有轻触事件。 它们之间的区别在于用户让系统栏重新显示出来的方式。

  • 向后倾斜模式
    当用户希望调出系统栏时,只需点按屏幕上的任意位置即可。

  • 沉浸模式
    当用户需要调出系统栏时,他们可从隐藏系统栏的任一边滑动。

  • 粘性沉浸模式
    在粘性沉浸模式下,如果用户从隐藏了系统栏的边缘滑动,系统栏会显示出来,但它们是半透明的,并且轻触手势会传递给应用,因此应用也会响应该手势。

    视频横向全屏通常用这个。

启用全屏模式

无论您想要使用哪种全屏模式,都必须调用 setSystemUiVisibility() 并将其传递给 SYSTEM_UI_FLAG_HIDE_NAVIGATION 和/或 SYSTEM_UI_FLAG_FULLSCREEN。您可以包含 SYSTEM_UI_FLAG_IMMERSIVE(用于普通沉浸模式)或 SYSTEM_UI_FLAG_IMMERSIVE_STICKY(用于粘性沉浸模式),也可以同时不含这两种标记以启用向后倾斜模式。

最好包含其他系统界面标志(例如 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_LAYOUT_STABLE),防止布局随着系统栏的隐藏和显示调整大小。您还应确保操作栏和其他界面控件同时处于隐藏状态。

全屏示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemUI();
}
}

private void hideSystemUI() {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}

// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showSystemUI() {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}

无操作一段时间后隐藏的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
// Note that system bars will only be "visible" if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// TODO: The system bars are visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
hideSystemUI();
}
}, 1000);
} else {
// TODO: The system bars are NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
}
}
});

Dialog

自定义Dialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CustomDialog extends Dialog {

public CustomDialog(@NonNull Context context) {
super(context);
}

public CustomDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}

protected CustomDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_custom);
}
}

DialogFragment

可以管理生命周期

https://developer.android.com/guide/fragments/dialogs?hl=zh-cn#java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CustomDialogFragment extends DialogFragment {

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return super.onCreateDialog(savedInstanceState);
}

@Override
public void onStart() {
super.onStart();
}

@Override
public void onResume() {
super.onResume();
}

@Override
public void onPause() {
super.onPause();
}

@Override
public void onStop() {
super.onStop();
}

@Override
public void onDestroyView() {
super.onDestroyView();
}

@Override
public void onDestroy() {
super.onDestroy();
}

@Override
public void onDetach() {
super.onDetach();
}
}
dependencies {
implementation 'androidx.fragment:fragment:1.3.6'
}