自定义View的一般步骤

自定义View应该是Android开发比较深入的内容,但又是在开发过程中必不可少的技能,总体来说,分为一下几步:

  • 在attr.xml文件里声明自定义View需要用到的参数,这些参数也是可以在布局文件中直接使用;
  • 布局文件中使用该View
  • 构造方法中获取自定义属性
  • 重写onMeasure()方法
  • 重写layout()方法
  • 重写onDraw()方法
  • 如有需要继续重写View继承过来的其他方法,前面的步骤也不是全部必须的,应根据项目需求灵活使用

    下面以自定义QQ5.0主页菜单SlidingMenu为例,介绍自定义View,项目Demo地址

1.如果没有attr.xml文件,可以建立

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="rightPadding" format="dimension"></attr>
<declare-styleable name="SlidingMenu">
<attr name="rightPadding"/>
</declare-styleable>
</resources>

rightPadding的含义为菜单展示时,菜单右侧距内容页面的距离

2.布局文件中使用该View

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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sliding="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq_com_bg_pic">
<com.gugutian.qqwapper.widget.SlidingMenu
android:id="@+id/main_sm"
android:layout_width="match_parent"
sliding:rightPadding="50dp"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="@layout/menu_main"/>
<LinearLayout
android:id="@+id/main_content_ly"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/main_content_fragment"/>
</LinearLayout>
</LinearLayout>
</com.gugutian.qqwapper.widget.SlidingMenu>
</LinearLayout>

SlidingMenu为自定义View,包含了菜单页跟内容页,具体可以参考QQ5.0页面。sliding:rightPadding="50dp"这里是使用自定义属性,前面记得还要声明该命名空间xmlns:sliding="http://schemas.android.com/apk/res-auto"

3.构造方法中获取自定义属性
具体获取自定义View的属性在携带三个参数的构造方法中

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
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
mLeftMenuPaddingRight = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
mLeftMenuPaddingRight,
context.getResources().getDisplayMetrics());
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
for(int i = 0, n = typedArray.getIndexCount(); i < n; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.SlidingMenu_rightPadding:
mLeftMenuPaddingRight = typedArray.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
mLeftMenuPaddingRight,
context.getResources().getDisplayMetrics()));
break;
}
}
typedArray.recycle();
}

4.重写onMeasure()方法
该方法测绘及设定该View所有的ChildView的大小

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!mOnce) {
mWapper = (LinearLayout) getChildAt(0);
mLeftMenuVg = (ViewGroup) mWapper.getChildAt(0);
mMainContentVg = (ViewGroup) mWapper.getChildAt(1);
mLeftMenuWidth = mLeftMenuVg.getLayoutParams().width = mScreenWidth - mLeftMenuPaddingRight;
mMainContentVg.getLayoutParams().width = mScreenWidth;
mOnce = !mOnce;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

5.重写layout()方法
设定该View所有ChildView的位置并在界面中展现

1
2
3
4
5
6
7
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
scrollTo(mLeftMenuWidth, 0);
}
}

6.其他

下面是完整的SlidingMenu的代码:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
public class SlidingMenu extends HorizontalScrollView{
private LinearLayout mWapper;
private ViewGroup mLeftMenuVg;
private ViewGroup mMainContentVg;
private boolean mOnce = false;
private int mScreenWidth;
// dp
private int mLeftMenuPaddingRight = 50;
private int mLeftMenuWidth;
private boolean mIsLeftMenuShown = false;
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
mLeftMenuPaddingRight = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
mLeftMenuPaddingRight,
context.getResources().getDisplayMetrics());
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
for(int i = 0, n = typedArray.getIndexCount(); i < n; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.SlidingMenu_rightPadding:
mLeftMenuPaddingRight = typedArray.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
mLeftMenuPaddingRight,
context.getResources().getDisplayMetrics()));
break;
}
}
typedArray.recycle();
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context) {
this(context, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!mOnce) {
mWapper = (LinearLayout) getChildAt(0);
mLeftMenuVg = (ViewGroup) mWapper.getChildAt(0);
mMainContentVg = (ViewGroup) mWapper.getChildAt(1);
mLeftMenuWidth = mLeftMenuVg.getLayoutParams().width = mScreenWidth - mLeftMenuPaddingRight;
mMainContentVg.getLayoutParams().width = mScreenWidth;
mOnce = !mOnce;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
scrollTo(mLeftMenuWidth, 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
if(scrollX >= mLeftMenuWidth / 2) {
smoothScrollTo(mLeftMenuWidth, 0);
mIsLeftMenuShown = false;
} else {
smoothScrollTo(0, 0);
mIsLeftMenuShown = true;
}
return true;
}
return super.onTouchEvent(ev);
}
public void showLeftMenu() {
if(!isLeftMenuShown()) {
smoothScrollTo(0, 0);
mIsLeftMenuShown = true;
}
}
public void hideLeftMenu() {
if(isLeftMenuShown()) {
smoothScrollTo(mLeftMenuWidth, 0);
mIsLeftMenuShown = false;
}
}
public boolean isLeftMenuShown() {
return mIsLeftMenuShown;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float scale = l * 1.0f / mLeftMenuWidth;//1.0~0 注意1.0f的乘法,使之成为float类型
float contentScale = 0.8f + 0.2f * scale;
ViewHelper.setPivotX(mMainContentVg, 0);
ViewHelper.setPivotY(mMainContentVg, mMainContentVg.getHeight() / 2);
ViewHelper.setScaleX(mMainContentVg, contentScale);
ViewHelper.setScaleY(mMainContentVg, contentScale);
float menuTranslationShow = 0.7f;
ViewHelper.setTranslationX(mLeftMenuVg, l * menuTranslationShow);
float menuScale = 1.0f - 0.2f * scale;
ViewHelper.setScaleX(mLeftMenuVg, menuScale);
ViewHelper.setScaleY(mLeftMenuVg, menuScale);
float menuAlphaScale = 1.0f - 0.5f * scale;
ViewHelper.setAlpha(mLeftMenuVg, menuAlphaScale);
}
}

基本上只要按照以上几步实现,理清其中的逻辑关系,这种模板式的自定义View还是比较简单的。

代码地址:https://github.com/xing634325131/QQWapper