在上一篇文章中,尝试了仿QQ5.0的侧滑菜单,但是,菜单展开时距离屏幕右侧的宽度(mMenuRightPadding)是在SlidingMenu.java中设定,现在,就来尝试把它设置成自定义属性,可以直接在布局文件中直接设定。
有关于自定义属性,可以参考:http://wujiandong.iteye.com/blog/1184921
第一步:书写xml文件(在res/values下建立,文件名可自定义),用于存放自定义的属性
在这里,可以把名字名设置为attr.xml,简单明了,使人一看就知道是有关于属性的。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="rightPadding" format="dimension" ></attr>
<declare-styleable name="SlidingMenu">
<attr name="rightPadding" ></attr>
</declare-styleable>
</resources>
format=”dimension” 中的dimension表示支持dp、sp等格式(如果只需要int类型,可将dimension改成int即可)
declare-styleable是给自定义控件添加自定义属性用的,相当于一个包含自定义属性元素的集合,在这里,只添加了一个元素
第二步:在布局文件中的根布局下定义命名空间,再进行使用
xmls:自定义空间名=”http://schemas.android.com/apk/res/+当前应用的包名”定义命名空间
接着,就可以像使用android:layout_width=”match_parent”(android为命名空间)一样使用自己的定义的属性了
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:test="http://schemas.android.com/apk/res/com.example.sideslipfollowqq5_0"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- SlidingMenu继承自HorizontalScrollView -->
<com.example.sideslipfollowqq5_0.SlidingMenu
android:layout_width="match_parent"
android:layout_height="match_parent"
test:rightPadding="150dp" > <!-- 使用自定义的属性,在这里设置为150dp-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" >
<include layout="@layout/left_menu"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq" >
</LinearLayout>
</LinearLayout>
</com.example.sideslipfollowqq5_0.SlidingMenu>
</RelativeLayout>
注意:
如果仿照attr.xml在values下写一个attr2.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="rightPadding2" format="dimension" ></attr>
<declare-styleable name="SlidingMenu2">
<attr name="rightPadding2" ></attr>
</declare-styleable>
</resources>
那么在布局文件中,可以依托上一个自定义的命名空间(test)来使用该属性,当然,也可以新定义一个命名空间,比如test2,然后test:rightPadding2="150dp"
第三步:在SlidingMenu.java的构造方法(含3个形参的)中获得在布局文件中设置的自定义属性的值,同时还要改写含1个形参以及含2个形参的构造函数
public class SlidingMenu extends HorizontalScrollView {
private LinearLayout mWapper;//activity_main.xml中com.example.sideslipfollowqq5_0.SlidingMenu布局
private ViewGroup mMenu;//activity_main.xml中include的布局
private ViewGroup mContent;//activity_main.xml中第二个LinearLayout布局
private int mScreenWidth;//屏幕的宽度
private int mMenuRightPadding;//菜单完全显示时与屏幕右侧的距离(在SlidingMenu中设置成一个固定值)
private boolean isOnce=true;//因为onMeasure又可能会多次调用(影响效率),这里用于判断onMeasure是否是第一次调用
private int mMenuWidth;//代表mMenu的宽度
// /**
// * 未使用自定义属性时调用
// * @param context
// * @param attrs
// */
// public SlidingMenu(Context context, AttributeSet attrs) {
// super(context, attrs);
//
// //获取屏幕的宽度
// WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// DisplayMetrics outMetrics=new DisplayMetrics();
// wm.getDefaultDisplay().getMetrics(outMetrics);//getMetrics将会为传入的outMetrics赋值
// mScreenWidth=outMetrics.widthPixels;
//
// //将50dp转化成相应的px
// //因为dp是相对于分辨率的,而px就是一个像素点,是固定大小的
// //为了在不同分辨率的屏幕上显示的效果相同,应该将mMenuRightPadding依据不同的分辨率而变换
// mMenuRightPadding=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
// }
public SlidingMenu(Context context) {
this(context,null);//改写构造参数,调用含2个形参的构造函数
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs,0);//改写构造参数,调用含3个形参的构造函数
}
/**
* 当自定义了属性时且使用时,会调用该方法
* @param context
* @param attrs
* @param defStyle
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);//注意,这里显示的调用父类的构造函数应该放在第一行,因为如果不这样,系统或自动调用一个父类的无参构造函数,而这是不存在的
//获取自己定义的属性,使用TypeArray类之后要用recycle释放
TypedArray a=context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyle, 0);//获得attr.xml中<declare-styleable></declare-styleable>下<attr></attr>组成的数组
int n=a.getIndexCount();//Return the number of indices in the array that actually have data.即获得数组中元素的个数
for(int i=0;i<n;i++) {
int attr = a.getIndex(i);//Return an index in the array that has data.即得到数组中第i个具有数据的元素的索引(下标识从0开始)
switch (attr) {
case R.styleable.SlidingMenu_rightPadding:
//SlidingMenu_rightPadding:"_"前面的为attr.xml中<declare-styleable></declare-styleable>的name,"_"后面的为<attr></attr>的name
//所以SlidingMenu_rightPadding表示名字为"SlidingMenu"的<declare-styleable></declare-styleable>中名字为"rightPadding"的<attr></attr>
//getDimensionPixelSize的第一个参数为attr,因为attr同时具有rightPadding设置的值的索引,传入该方法后会根据索引获取rightPadding设置的值
//getDimensionPixelSize的第二个参数是设置一个默认的值,这里是为了给mMenuRightPadding设置了默认的值50以后
mMenuRightPadding=a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));
break;
default:
break;
}
}
a.recycle();//释放资源
//获取屏幕的宽度
WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics=new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);//getMetrics将会为传入的outMetrics赋值
mScreenWidth=outMetrics.widthPixels;
//给mMenuRightPadding设置了默认的值50以后,这一步就不需要了
//将50dp转化成相应的px
//因为dp是相对于分辨率的,而px就是一个像素点,是固定大小的
//为了在不同分辨率的屏幕上显示的效果相同,应该将mMenuRightPadding依据不同的分辨率而变换
//mMenuRightPadding=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
}
/**
* 设置子View的宽和高以及自己的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isOnce) {
mWapper = (LinearLayout) getChildAt(0);// 得到activity_main.xml中SlidingMenu布局下的第一个元素,即activity_main.xml中第一个LinearLayout
mMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
mMenuWidth=mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;// 设置mMenu的宽度
mContent.getLayoutParams().width = mScreenWidth;
isOnce = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 通过设置偏移量,将menu隐藏
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(mMenuWidth, 0);// 当第一个参数为正时,滚动条向左移动,内容区域向右移动
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action=ev.getAction();//通过action判断用户的操作,是按下、抬起、还是在移动
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX=(int) getScrollX();//得到未在屏幕显示出的内容的左侧区域宽度(即左侧隐藏的宽度),动态变化的
if(scrollX>=mMenuWidth/2) {
this.smoothScrollTo(mMenuWidth, 0);
//this.scrollTo(mMenuWidth, 0);
//如果内容隐藏的区域>=菜单的一半,隐藏菜单
//smoothScrollTo方法与scroolTo相似,只不过scroolTo是瞬间完成,而该方法有一个缓冲的效果
}
else {
this.smoothScrollTo(0, 0);
}
return true;
default:
break;
}
return super.onTouchEvent(ev);
}
}
getDimension
获取某个dimen的值,如果是dp或sp的单位,将其乘以density,如果是px,则不乘 返回float
getDimensionPixelOffset
获取某个dimen的值,如果是dp或sp的单位,将其乘以density,如果是px,则不乘 返回int
getDimensionPixelSize
则不管写的是dp还是sp还是px,都会乘以denstiy.
在类TypedArray和类Resources中都有这三个函数,功能类似,TypedArray中的函数是获取自定义属性的,Resources中的函数是获取android预置属性的
最后,就可以运行看看效果了