目录
Android自定义控件从零开始-第一篇 View的绘制流程 中我们简单了解了View绘制最基础的一些流程和API,本文就基于这些最基础的内容,实现一些简单的自定义样式。
1、自定义ToorBar
Android开发中最常见的就是顶部的标题栏,一般来说包含左边的返回键、中间的标题和右边的菜单,一个应用最低包含十几个页面,相比每个页面都重写一个标题栏布局,统一自定义一个标题控件不管是今后统一维护还是拓展功能会更加方便。
(1)创建标题栏布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_80"
android:background="@color/colorToolbar"
android:orientation="vertical">
<!--沉浸式-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_30"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_50">
<!--左边返回可能是返回箭头和文本的组合-->
<RelativeLayout
android:id="@+id/rl_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:onClick="onBackView">
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:paddingLeft="@dimen/dp_18"
android:src="@mipmap/ic_toolbar_back" />
<TextView
android:id="@+id/tv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toRightOf="@id/iv_back"
android:gravity="center"
android:text=""
android:textColor="@color/colorWhiteFont"
android:textSize="@dimen/sp_14" />
</RelativeLayout>
<!--中间标题文本-->
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="title"
android:textColor="@color/colorWhiteFont"
android:textSize="@dimen/sp_18" />
<!--右边菜单有文本或者图标两种-->
<ImageButton
android:id="@+id/img_btn_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:paddingRight="@dimen/dp_18"
android:visibility="visible" />
<TextView
android:id="@+id/tv_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:gravity="center"
android:paddingLeft="10dp"
android:paddingRight="@dimen/dp_18"
android:text="menu"
android:textColor="@color/colorWhiteFont"
android:textSize="@dimen/sp_14"
android:visibility="visible" />
</RelativeLayout>
</LinearLayout>
效果如图:
基础的布局效果出来了,然后需要继承一个已有的ViewGroup,将这个布局加入进去,并对标题栏上子View进行操控。
(2)创建TooBarLayout
直接上代码
public class TooBarLayout extends RelativeLayout {
protected View rootView;
protected ImageView mIvBackBtn;
protected TextView mTvTitle;
protected ImageButton mImgBtnMenu;
protected TextView mTvMenu;
protected TextView mTvBack;
protected RelativeLayout mRlBack;
public TooBarLayout(Context context) {
this(context, null);
}
public TooBarLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TooBarLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context, attrs, defStyle);
}
private void initView(Context context, AttributeSet attrs, int defStyle) {
rootView = LayoutInflater.from(context).inflate(R.layout.layout_toobar, this, false);
mIvBackBtn = (ImageView) rootView.findViewById(R.id.iv_back);
mTvTitle = (TextView) rootView.findViewById(R.id.tv_title);
mTvMenu = (TextView) rootView.findViewById(R.id.tv_menu);
mImgBtnMenu = (ImageButton) rootView.findViewById(R.id.img_btn_menu);
mTvBack = (TextView) rootView.findViewById(R.id.tv_back);
mRlBack = (RelativeLayout) rootView.findViewById(R.id.rl_back);
//将标题栏布局加入进去
this.addView(rootView);
}
}
通过addView直接将写好的xml加入进去,但是现在这个自定义控件还没有实用价值,我们希望和已有的控件一样,在xml中修改文本或者让按钮显示隐藏。这里就需要用到TypedArray属性的容器,在values中创建一个attrs.xml文件,然后加入一些和标题栏相关的属性:
<declare-styleable name="TooBarLayout">
<!--是否显示返回按钮-->
<attr name="tl_isShowBackBtn" format="boolean" />
<!--是否显示标题-->
<attr name="tl_isShowTitle" format="boolean" />
<!--显示右上角的菜单-->
<attr name="tl_showMenu" format="enum">
<enum name="none" value="0"/>
<enum name="text" value="1"/>
<enum name="imgbtn" value="2"/>
</attr>
<!--返回文本内容-->
<attr name="tl_backText" format="string"/>
<!--返回按钮图片-->
<attr name="tl_backImg" format="reference" />
<!--标题文本大小-->
<attr name="tl_titleSize" format="dimension"/>
<!--标题文本颜色-->
<attr name="tl_titleColor" format="color"/>
<!--标题文本内容-->
<attr name="tl_titleText" format="string"/>
<!--右上角菜单文本内容-->
<attr name="tl_tvMenuText" format="string"/>
<!--右上角菜单文本大小-->
<attr name="tl_tvMenuTextSize" format="dimension"/>
<!--右上角菜单文本颜色-->
<attr name="tl_tvMenuTextColor" format="color"/>
<!--右上角菜单图片-->
<attr name="tl_btnMenuSrc" format="reference"/>
</declare-styleable>
然后在TooBarLayou中获取这些属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TooBarLayout, defStyle, 0);
isShowBackBtn = a.getBoolean(R.styleable.TooBarLayout_tl_isShowBackBtn, isShowBackBtn);
isShowTitle = a.getBoolean(R.styleable.TooBarLayout_tl_isShowTitle, isShowTitle);
showMenuType = a.getInt(R.styleable.TooBarLayout_tl_showMenu, MENU_NONE);
titleSize = a.getDimensionPixelSize(R.styleable.TooBarLayout_tl_titleSize, 18);
titleColor = a.getColor(R.styleable.TooBarLayout_tl_titleColor, titleColor);
tvMenuText = a.getString(R.styleable.TooBarLayout_tl_tvMenuText);
tvMenuTextSize = a.getDimensionPixelSize(R.styleable.TooBarLayout_tl_tvMenuTextSize, 16);
tvMenuTextColor = a.getColor(R.styleable.TooBarLayout_tl_tvMenuTextColor, tvMenuTextColor);
btnMenuSrc = a.getResourceId(R.styleable.TooBarLayout_tl_btnMenuSrc, 0);
tvTitleText = a.getString(R.styleable.TooBarLayout_tl_titleText);
tvBackText = a.getString(R.styleable.TooBarLayout_tl_backText);
backSrc = a.getResourceId(R.styleable.TooBarLayout_tl_backImg, 0);
a.recycle();
有了这些属性,我们就可以在初始化时直接修改我们自定义的标题栏
if (!isShowBackBtn) mRlBack.setVisibility(GONE);
else mRlBack.setVisibility(VISIBLE);
if (!isShowTitle) mTvTitle.setVisibility(GONE);
else mTvTitle.setVisibility(VISIBLE);
mTvTitle.setTextSize(titleSize);
mTvTitle.setTextColor(titleColor);
mTvTitle.setText(tvTitleText);
mTvBack.setText(tvBackText);
...
针对TooBarLayout的事件,我们也可以定义一些interface类,将各个控件的事件通过接口回调暴露出去。
(3)使用
直接在页面中中引用TooBarLayout,在根目录下加入
xmlns:app="http://schemas.android.com/apk/res-auto"
保证引用的TooBarLayout能使用自定义的属性,如下:
<com.example.myapplication.simple.TooBarLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_80"
app:tl_titleText="Title"
app:tl_isShowTitle="true"
app:tl_showMenu="imgbtn"
app:tl_backImg="@mipmap/ic_menu"
app:tl_backText="Back"
app:tl_titleColor="@color/red"
app:tl_btnMenuSrc="@mipmap/ic_menu"
app:tl_isShowBackBtn="true"
app:tl_titleSize="@dimen/dp_20"
app:tl_tvMenuText="Menu"
app:tl_tvMenuTextColor="@color/yellow"
app:tl_tvMenuTextSize="@dimen/dp_20"/>
2、跑马灯
走马灯的原理很简单,就是对文本的不断重绘,每次重绘将文本往左边移动一点,视觉效果上就能达到文本不断像左边移动的效果。我们只实现简单的效果:文本向左边滚动,滚动一次完全结束后继续第二次滚动。
(1)文本绘制和文本x坐标的概念
文本的绘制很简单,直接使用drawText(String text, float x, float y, Paint paint) 就可以把文本画出来,需要注意的是这里的y坐标通过rect.height() 拿到的会比实际上大一些,会导致绘制时文本偏下,因此采用以下方法:
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float height = Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2;
这里需要明白,跑马灯里面的文本并没有减少,改变的是位置,看不到的部分是在屏幕外面,但并不代表不存在,下图简单勾画了文本内容在屏幕不同位置,可以很清楚的看到文本在屏幕内和屏幕外时坐标x的改变。
(2)定义属性和创建ScrollTextView
只定义了两个属性,一个文本大小一个文本颜色,ScrollTextView继承View。
<!--跑马灯-->
<declare-styleable name="ScrollTextView">
<attr name="text_color" format="color" /><!-- 文字颜色 -->
<attr name="text_size" format="dimension" /><!-- 文字大小 -->
</declare-styleable>
public class ScrollTextView extends View {}
(3)实现文本的展示和移动
移动需要用到线程每隔10s重绘文本,每次重绘将文本的x坐标减少一点,达到移动的效果
@Override
public void run() {
while (isRoll && !TextUtils.isEmpty(content)) {
try {
Thread.sleep(10);
textX = textX - speed;
postInvalidate();//每隔10毫秒重绘视图
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
具体的绘制还是依靠draw方法,这里面有个对文本x坐标的判断,文本最左边到屏幕左边的距离已经超过了文本的内容,表明文本在屏幕上无法看见,这个时候需要重置文本x坐标的值,让文本重新从最右边出现。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文本宽度小于等于文本X坐标,也就是说文本已经到头了,重置文本x坐标
if (contentWidth <= (-textX)) {
textX = getWidth();
}
//把文字画出来
if (string != null) {
canvas.drawText(string, textX, getHeight() / 2 + textHeight / 2, paint);
}
}