本文主要总结一下笔者实际项目中碰到的自定义View的几种方式,以及优劣。
大概有以下几种方式:
本文更多的还是个人使用经验的总结,如有错误或者需要补充的地方,欢迎评论交流。
demo中分别使用三种方式实现了类似的布局。除了作者将列出的优劣之外,读者可自行查看代码来进行比较。
实现效果与项目文件如下:
优势
劣势
适合场景
不适合需要在xml中直接调用的场景,其他大部分场景都可以适用。
public class TestViewOne extends LinearLayout {
//ui
private TextView tvTitle;
private TextView tvMessage;
public TestViewOne(Context context) {
this(context, null);
}
public TestViewOne(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TestViewOne(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override protected void onFinishInflate() {
super.onFinishInflate();
bindView();
}
private void bindView() {
tvTitle = findViewById(R.id.tv_title_view_one);
tvMessage = findViewById(R.id.tv_message_view_one);
}
public void setTitle(String title) {
tvTitle.setText(title);
}
public void setMessage(String message) {
tvMessage.setText(message);
}
}
<com.example.multiviewtest.TestViewOne xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="20dp"
>
<TextView
android:id="@+id/tv_title_view_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
tools:text="@string/test_view_title"
/>
<TextView
android:id="@+id/tv_message_view_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
tools:text="@string/test_view_message"
/>
com.example.multiviewtest.TestViewOne>
优势
劣势
适合场景
适用于页面布局不复杂的情况。
也适用于给原生的控件增加拓展的场景,比如想给原生的LinearLayout增加header。
public class TestViewTwo extends LinearLayout {
//data
private Context context;
//ui
private TextView tvTitle;
private TextView tvMessage;
public TestViewTwo(Context context) {
this(context, null);
}
public TestViewTwo(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TestViewTwo(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
int padding = DisplayUtils.dpToPx(context, 20);//20dp
setOrientation(VERTICAL);
setPadding(padding, padding, padding, padding);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(context, 150)));
createChildView();
}
private void createChildView() {
tvTitle = new TextView(this.context);
tvTitle.setTextSize(30);
tvTitle.setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
this.addView(tvTitle);
tvMessage = new TextView(this.context);
tvMessage.setTextSize(20);
tvMessage.setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
this.addView(tvMessage);
}
public void setTitle(String title) {
tvTitle.setText(title);
}
public void setMessage(String message) {
tvMessage.setText(message);
}
}
优势
劣势
适合场景
适用于所有场景,尤其是页面布局复杂的场景。
(与方案一相比,这种方案也可以再xml中直接调用)
这种方案中,xml中的最外层布局一定要使用merge,否则会增加一层毫无意义的嵌套。
将原来最外层的布局改成merge后,会发现原来的布局无法预览了。此时,给merge加上“tools:parentTag”这个参数就可以了。
public class TestViewThree extends LinearLayout {
//ui
private TextView tvTitle;
private TextView tvMessage;
public TestViewThree(Context context) {
this(context, null);
}
public TestViewThree(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TestViewThree(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.view_test_three, this, true);
int padding = DisplayUtils.dpToPx(context, 20);//20dp
setOrientation(VERTICAL);
setPadding(padding, padding, padding, padding);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(context, 150)));
initViews();
}
private void initViews() {
tvTitle = findViewById(R.id.tv_title_view_one);
tvMessage = findViewById(R.id.tv_message_view_one);
}
public void setTitle(String title) {
tvTitle.setText(title);
}
public void setMessage(String message) {
tvMessage.setText(message);
}
}
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="20dp"
tools:parentTag="android.widget.LinearLayout"
>
<TextView
android:id="@+id/tv_title_view_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
tools:text="@string/test_view_title"
/>
<TextView
android:id="@+id/tv_message_view_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
tools:text="@string/test_view_message"
/>
merge>
public class MainActivity extends AppCompatActivity {
//ui
private LinearLayout llRoot;
private TestViewOne testViewOne;
private TestViewTwo testViewTwo;
private TestViewThree testViewThree;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
llRoot = findViewById(R.id.ll_root_main);
testViewTwo = findViewById(R.id.tv_test_two_main);
testViewTwo.setTitle("test two title");
testViewTwo.setMessage("test two message");
testViewThree = findViewById(R.id.tv_test_three_main);
testViewThree.setTitle("test three title");
testViewThree.setMessage("test three message");
testViewOne =
(TestViewOne) LayoutInflater.from(this).inflate(R.layout.view_test_one, null, false);
testViewOne.setTitle("test one title");
testViewOne.setMessage("test one message");
llRoot.addView(testViewOne);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_root_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
>
<com.example.multiviewtest.TestViewTwo
android:id="@+id/tv_test_two_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<com.example.multiviewtest.TestViewThree
android:id="@+id/tv_test_three_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
LinearLayout>
由于实现中会存在单位的差异,因此单独定义了这个工具类来负责单位转化。
public class DisplayUtils {
public static int dpToPx(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue * scale);
}
}
个人最推荐方案三,原因有以下几点: