Android 如何创建简单的自定义 UI 元素
Android how to create simple custom UI elements
我想在 Android 中创建简单的自定义 UI 元素,就像屏幕截图中的元素一样:
灯泡的大小应始终相同,但矩形的宽度应有所不同。这样做的一种选择是使用 Canvas 元素。但我想问一下是否还有更简单的方法来做到这一点。是否可以仅通过使用 XML 文件来执行此操作?我想在 LayoutEditor 中使用这些 UI 元素,例如我可以在 XML 布局文件中或以编程方式调整宽度和高度的 TextView。
知道如何以简单的方式做到这一点吗?
Update:我尝试了 Cheticamp 建议的方法,我的片段中有以下代码:
public class Test extends Fragment implements Runnable {
/*
Game variables
*/
public static final int DELAY_MILLIS = 100;
public static final int TIME_OF_A_LEVEL_IN_SECONDS = 90;
private int currentTimeLeftInTheLevel_MILLIS;
private Handler handler = new Handler();
private FragmentGameBinding binding;
private boolean viewHasBeenCreated = false;
public Test() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentGameBinding.inflate(inflater, container, false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
container.getContext();
viewHasBeenCreated = true;
startRound();
return binding.getRoot();
}
public void startRound () {
currentTimeLeftInTheLevel_MILLIS =TIME_OF_A_LEVEL_IN_SECONDS * 1000;
updateScreen();
handler.postDelayed(this, 1000);
}
private void updateScreen() {
binding.textViewTimeLeftValue.setText("" + currentTimeLeftInTheLevel_MILLIS/1000);
/*
IMPORTANT PART: This should create a simple custom UI element but it creates an error
*/
View view = new View(getActivity());
view.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
Drawable dr = ContextCompat.getDrawable(getActivity(),R.drawable.light_bulb_layer_list);
view.setBackground(dr);
ConstraintLayout constraintLayout = binding.constraintLayout;
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.connect(view.getId(),ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0);
constraintSet.connect(view.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID ,ConstraintSet.TOP,0);
constraintSet.connect(view.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID ,ConstraintSet.LEFT,0);
constraintSet.connect(view.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID ,ConstraintSet.RIGHT,0);
constraintSet.setHorizontalBias(view.getId(), 0.16f);
constraintSet.setVerticalBias(view.getId(), 0.26f);
constraintSet.applyTo(constraintLayout);
}
private void countDownTime(){
currentTimeLeftInTheLevel_MILLIS = currentTimeLeftInTheLevel_MILLIS -DELAY_MILLIS;
updateScreen();
}
@Override
public void run() {
if(viewHasBeenCreated) {
countDownTime();
}
}
}
不幸的是,此代码导致“java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法 'boolean android.content.Context.isUiContext()'”。它由 View view = new View(getActivity());
行抛出。这是完整的错误消息:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.game, PID: 12176
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Context.isUiContext()' on a null object reference
at android.view.ViewConfiguration.get(ViewConfiguration.java:502)
at android.view.View.<init>(View.java:5317)
at com.example.game.Test.updateScreen(Test.java:72)
at com.example.game.Test.countDownTime(Test.java:91)
at com.example.game.Test.run(Test.java:97)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
知道问题出在哪里吗?没有自定义 UI 元素,片段工作正常。
没问题。
在这种情况下,像这样的简单 xml 文件就足够了。让我们在 layout
文件夹中将其命名为 something.xml
。
<LinearLayout ...>
<ImageView ...>
</LinearLayout>
在另一个布局 xml 文件中,您可以:
<ConstraintLayout ...>
<include android:id="@+id/something"" layout="@layout/something" android:layout_width="70dp">
</ConstraintLayout>
如果您想获得 children,您可以随时在 Activity 或片段上使用 findViewById
来获得它们。如果您正在使用数据绑定或视图绑定,它会变得更好:它们将显示为 XBinding
class 中的字段,这些字段是从 XML 文件
中生成的
您好 VanessaF,请进一步说明您在评论中提出的问题:
<include />
<include />
标签是一个特殊的 XML 标签,我们可以在 Android XML 布局文件中使用它来指示我们放置 <include/>
我们希望将其替换为通过 <include />
标签内的 layout
属性确定的其他 XML。
这是一个例子:
考虑layout/example.xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello!"/>
并考虑 layout/parent.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button .../>
<include layout="@layout/example"/>
<ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>
每当我在某处使用 R.layout.parent
时(例如在 Activity
中的 setContent
中,将生成的视图如下所示:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button .../>
<!-- PLEASE NOTICE THAT <include/> IS GONE -->
<!-- AND HAS BEEN REPLACED WITH THE CONTENTS the specified layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello!"/>
<ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>
无需编写 full-blown 自定义视图即可有效 re-using 布局。
注意:您在 <include/>
标签内指定的所有属性将有效地覆盖布局文件内指定的其他属性。让我用一个例子来说明这一点:
再考虑一下layout/example.xml
。请注意,这次 TextView
将在高度和宽度上都缩小到文本的大小。
<TextView
android:text="Hello!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
并考虑 parent:layout/parent.xml
。请注意,我正在设置属性 android:layout_width
和 android:layout_height
.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
layout="@layout/example"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
在这种情况下,当 Android 将 @layout/example
的内容替换为 <include/>
时,它还会设置 android:layout_width="match_parent"
和 android:layout_height="match_parent"
,因为它们是在<include/>
标签有效地忽略了 layout/example.xml
中设置的原始属性(设置为“wrap_content”)
使用 TextView。灯泡可以是左compound drawable. Set the background to a rounded rectangle shape drawable. This can all be specified in XML. See TextView.
如果不需要文本,也可以使用 LayerList 可绘制对象来完成。 (TextView 解决方案也可以在没有文本的情况下使用 - 只需将文本设置为“”或 null。)
<layer-list>
<item>
<shape android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="#FF9800" />
</shape>
</item>
<item
android:drawable="@drawable/ic_baseline_lightbulb_24"
android:width="48dp"
android:height="48dp"
android:gravity="left|center_vertical" />
</layer-list>
图层列表设置为简单View的背景。
<View
android:layout_width="250dp"
android:layout_height="56dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/light_bulb_layer_list" />
要在代码中创建 View:
View view = new View(context);
view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
Drawable dr = ContextCompat.getDrawable(context,R.drawable.light_bulb_layer_list)
view.setBackground(dr);
我建议阅读Custom View Components from the official Android documentation。事实上,对于您使用 Android 应用程序所做的一切,您应该非常熟悉此文档。
我想在 Android 中创建简单的自定义 UI 元素,就像屏幕截图中的元素一样:
灯泡的大小应始终相同,但矩形的宽度应有所不同。这样做的一种选择是使用 Canvas 元素。但我想问一下是否还有更简单的方法来做到这一点。是否可以仅通过使用 XML 文件来执行此操作?我想在 LayoutEditor 中使用这些 UI 元素,例如我可以在 XML 布局文件中或以编程方式调整宽度和高度的 TextView。
知道如何以简单的方式做到这一点吗?
Update:我尝试了 Cheticamp 建议的方法,我的片段中有以下代码:
public class Test extends Fragment implements Runnable {
/*
Game variables
*/
public static final int DELAY_MILLIS = 100;
public static final int TIME_OF_A_LEVEL_IN_SECONDS = 90;
private int currentTimeLeftInTheLevel_MILLIS;
private Handler handler = new Handler();
private FragmentGameBinding binding;
private boolean viewHasBeenCreated = false;
public Test() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentGameBinding.inflate(inflater, container, false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
container.getContext();
viewHasBeenCreated = true;
startRound();
return binding.getRoot();
}
public void startRound () {
currentTimeLeftInTheLevel_MILLIS =TIME_OF_A_LEVEL_IN_SECONDS * 1000;
updateScreen();
handler.postDelayed(this, 1000);
}
private void updateScreen() {
binding.textViewTimeLeftValue.setText("" + currentTimeLeftInTheLevel_MILLIS/1000);
/*
IMPORTANT PART: This should create a simple custom UI element but it creates an error
*/
View view = new View(getActivity());
view.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
Drawable dr = ContextCompat.getDrawable(getActivity(),R.drawable.light_bulb_layer_list);
view.setBackground(dr);
ConstraintLayout constraintLayout = binding.constraintLayout;
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.connect(view.getId(),ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0);
constraintSet.connect(view.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID ,ConstraintSet.TOP,0);
constraintSet.connect(view.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID ,ConstraintSet.LEFT,0);
constraintSet.connect(view.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID ,ConstraintSet.RIGHT,0);
constraintSet.setHorizontalBias(view.getId(), 0.16f);
constraintSet.setVerticalBias(view.getId(), 0.26f);
constraintSet.applyTo(constraintLayout);
}
private void countDownTime(){
currentTimeLeftInTheLevel_MILLIS = currentTimeLeftInTheLevel_MILLIS -DELAY_MILLIS;
updateScreen();
}
@Override
public void run() {
if(viewHasBeenCreated) {
countDownTime();
}
}
}
不幸的是,此代码导致“java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法 'boolean android.content.Context.isUiContext()'”。它由 View view = new View(getActivity());
行抛出。这是完整的错误消息:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.game, PID: 12176
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Context.isUiContext()' on a null object reference
at android.view.ViewConfiguration.get(ViewConfiguration.java:502)
at android.view.View.<init>(View.java:5317)
at com.example.game.Test.updateScreen(Test.java:72)
at com.example.game.Test.countDownTime(Test.java:91)
at com.example.game.Test.run(Test.java:97)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
知道问题出在哪里吗?没有自定义 UI 元素,片段工作正常。
没问题。
在这种情况下,像这样的简单 xml 文件就足够了。让我们在 layout
文件夹中将其命名为 something.xml
。
<LinearLayout ...>
<ImageView ...>
</LinearLayout>
在另一个布局 xml 文件中,您可以:
<ConstraintLayout ...>
<include android:id="@+id/something"" layout="@layout/something" android:layout_width="70dp">
</ConstraintLayout>
如果您想获得 children,您可以随时在 Activity 或片段上使用 findViewById
来获得它们。如果您正在使用数据绑定或视图绑定,它会变得更好:它们将显示为 XBinding
class 中的字段,这些字段是从 XML 文件
您好 VanessaF,请进一步说明您在评论中提出的问题:
<include />
<include />
标签是一个特殊的 XML 标签,我们可以在 Android XML 布局文件中使用它来指示我们放置 <include/>
我们希望将其替换为通过 <include />
标签内的 layout
属性确定的其他 XML。
这是一个例子:
考虑layout/example.xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello!"/>
并考虑 layout/parent.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button .../>
<include layout="@layout/example"/>
<ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>
每当我在某处使用 R.layout.parent
时(例如在 Activity
中的 setContent
中,将生成的视图如下所示:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button .../>
<!-- PLEASE NOTICE THAT <include/> IS GONE -->
<!-- AND HAS BEEN REPLACED WITH THE CONTENTS the specified layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello!"/>
<ImageView android:drawable="@drawable/ic_send"/>
</LinearLayout>
无需编写 full-blown 自定义视图即可有效 re-using 布局。
注意:您在 <include/>
标签内指定的所有属性将有效地覆盖布局文件内指定的其他属性。让我用一个例子来说明这一点:
再考虑一下layout/example.xml
。请注意,这次 TextView
将在高度和宽度上都缩小到文本的大小。
<TextView
android:text="Hello!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
并考虑 parent:layout/parent.xml
。请注意,我正在设置属性 android:layout_width
和 android:layout_height
.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
layout="@layout/example"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
在这种情况下,当 Android 将 @layout/example
的内容替换为 <include/>
时,它还会设置 android:layout_width="match_parent"
和 android:layout_height="match_parent"
,因为它们是在<include/>
标签有效地忽略了 layout/example.xml
中设置的原始属性(设置为“wrap_content”)
使用 TextView。灯泡可以是左compound drawable. Set the background to a rounded rectangle shape drawable. This can all be specified in XML. See TextView.
如果不需要文本,也可以使用 LayerList 可绘制对象来完成。 (TextView 解决方案也可以在没有文本的情况下使用 - 只需将文本设置为“”或 null。)
<layer-list>
<item>
<shape android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="#FF9800" />
</shape>
</item>
<item
android:drawable="@drawable/ic_baseline_lightbulb_24"
android:width="48dp"
android:height="48dp"
android:gravity="left|center_vertical" />
</layer-list>
图层列表设置为简单View的背景。
<View
android:layout_width="250dp"
android:layout_height="56dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/light_bulb_layer_list" />
要在代码中创建 View:
View view = new View(context);
view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
Drawable dr = ContextCompat.getDrawable(context,R.drawable.light_bulb_layer_list)
view.setBackground(dr);
我建议阅读Custom View Components from the official Android documentation。事实上,对于您使用 Android 应用程序所做的一切,您应该非常熟悉此文档。