正文
版权声明:本文为博主原创文章,未经博主允许不得转载
系列教程:
Android开发之从零开始系列
源码:
AnliaLee/ExpandMenu
,欢迎star
大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论
前言
:最近项目里要实现一个
可展开收起的水平菜单栏
控件,刚接到需求时想着用自定义View自己来绘制,发现要实现
圆角、阴影、菜单滑动等效果
非常复杂且耗时间。好在这些效果
Android原生代码
中都已经有非常成熟的解决方案,我们只需要去继承它们进行
二次开发
就行。本期将教大家如何
继承ViewGroup(RelativeLayout)实现自定义菜单栏
本篇只着重于思路和实现步骤,里面用到的一些知识原理不会非常细地拿来讲,如果有不清楚的api或方法可以在网上搜下相应的资料,肯定有大神讲得非常清楚的,我这就不献丑了。本着认真负责的精神我会把相关知识的博文链接也贴出来(其实就是懒不想写那么多哈哈),大家可以自行传送。为了照顾第一次阅读系列博客的小伙伴,本篇会出现一些在之前
系列博客
就讲过的内容,看过的童鞋自行跳过该段即可
国际惯例,先上效果图
为菜单栏设置背景
自定义ViewGroup
和
自定义View
的绘制过程有所不同,
View
可以直接在自己的
onDraw
方法中绘制所需要的效果,而
ViewGroup
会先
测量子View的大小位置(onLayout)
,然后再进行绘制,如果
子View或background为空
,则
不会调用draw方法绘制
。当然我们可以调用
setWillNotDraw(false)
让
ViewGroup
可以在
子View或background为空
的情况下进行绘制,但我们会为
ViewGroup
设置一个默认背景,所以可以省去这句代码
设置背景很简单,因为要实现圆角、描边等效果,所以我们选择使用
GradientDrawable
来定制背景,然后调用
setBackground
方法设置背景。创建
HorizontalExpandMenu
,继承自
RelativeLayout
,同时
自定义Attrs属性
public class HorizontalExpandMenu extends RelativeLayout {
private Context mContext;
private AttributeSet mAttrs;
private int defaultWidth;
private int defaultHeight;
private int viewWidth;
private int viewHeight;
private int menuBackColor;
private float menuStrokeSize;
private int menuStrokeColor;
private float menuCornerRadius;
public HorizontalExpandMenu(Context context) {
super(context);
this.mContext = context;
init();
}
public HorizontalExpandMenu(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
this.mAttrs = attrs;
init();
}
private void init(){
TypedArray typedArray = mContext.obtainStyledAttributes(mAttrs, R.styleable.HorizontalExpandMenu);
defaultWidth = DpOrPxUtils.dip2px(mContext,200);
defaultHeight = DpOrPxUtils.dip2px(mContext,40);
menuBackColor = typedArray.getColor(R.styleable.HorizontalExpandMenu_back_color,Color.WHITE);
menuStrokeSize = typedArray.getDimension(R.styleable.HorizontalExpandMenu_stroke_size,1);
menuStrokeColor = typedArray.getColor(R.styleable.HorizontalExpandMenu_stroke_color,Color.GRAY);
menuCornerRadius = typedArray.getDimension(R.styleable.HorizontalExpandMenu_corner_radius,DpOrPxUtils.dip2px(mContext,20));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = measureSize(defaultHeight, heightMeasureSpec);
int width = measureSize(defaultWidth, widthMeasureSpec);
viewHeight = height;
viewWidth = width;
setMeasuredDimension(viewWidth,viewHeight);
if(getBackground()==null){
setMenuBackground();
}
}
private int measureSize(int defaultSize, int measureSpec) {
int result = defaultSize;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
if (specMode == View.MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == View.MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
private void setMenuBackground(){
GradientDrawable gd = new GradientDrawable();
gd.setColor(menuBackColor);
gd.setStroke((int)menuStrokeSize, menuStrokeColor);
gd.setCornerRadius(menuCornerRadius);
setBackground(gd);
}
}
attrs属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HorizontalExpandMenu">
<attr name="back_color" format="color"></attr>
<attr name="stroke_size" format="dimension"></attr>
<attr name="stroke_color" format="color"></attr>
<attr name="corner_radius" format="dimension"></attr>
</declare-styleable>
</resources>
在布局文件中使用
<com.anlia.expandmenu.widget.HorizontalExpandMenu
android:id="@+id/expandMenu1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp">
</com.anlia.expandmenu.widget.HorizontalExpandMenu>
效果如图
绘制菜单栏按钮
我们要绘制菜单栏两边的
按钮
,首先是要为按钮
圈地(测量位置和大小)
。设置
按钮区域为正方形
,位于
左侧或右侧(根据开发者设置而定)
,
边长和菜单栏ViewGroup的高相等
。按钮中的加号可以使用
Path
进行绘制(当然也可以用
矢量图
或
位图
),代码如下
public class HorizontalExpandMenu extends RelativeLayout {
private float buttonIconDegrees;
private float buttonIconSize;
private float buttonIconStrokeWidth;
private int buttonIconColor;
private int buttonStyle;
private int buttonRadius;
private float buttonTop;
private float buttonBottom;
private Point rightButtonCenter;
private float rightButtonLeft;
private float rightButtonRight;
private Point leftButtonCenter;
private float leftButtonLeft;
private float leftButtonRight;
public class ButtonStyle {
public static final int Right = 0;
public static final int Left = 1;
}
private void init(){
buttonStyle = typedArray.getInteger(R.styleable.HorizontalExpandMenu_button_style,ButtonStyle.Right);
buttonIconDegrees = 0;
buttonIconSize = typedArray.getDimension(R.styleable.HorizontalExpandMenu_button_icon_size,DpOrPxUtils.dip2px(mContext,8));
buttonIconStrokeWidth = typedArray.getDimension(R.styleable.HorizontalExpandMenu_button_icon_stroke_width,8);
buttonIconColor = typedArray.getColor(R.styleable.HorizontalExpandMenu_button_icon_color,Color.GRAY);
buttonIconPaint = new Paint();
buttonIconPaint.setColor(buttonIconColor);
buttonIconPaint.setStyle(Paint.Style.STROKE);
buttonIconPaint.setStrokeWidth(buttonIconStrokeWidth);
buttonIconPaint.setAntiAlias(true);
path = new Path();
leftButtonCenter = new Point();
rightButtonCenter = new Point();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = measureSize(defaultHeight, heightMeasureSpec);
int width = measureSize(defaultWidth, widthMeasureSpec);
viewHeight = height;
viewWidth = width;
setMeasuredDimension(viewWidth,viewHeight);
buttonRadius = viewHeight/2;
layoutRootButton();
if(getBackground()==null){
setMenuBackground();
}
}
@Override
protected void onDraw(Canvas canvas) {
layoutRootButton();
if(buttonStyle == ButtonStyle.Right){
drawRightIcon(canvas);
}else {
drawLeftIcon(canvas);
}
super.onDraw(canvas);
}
private void layoutRootButton(){
buttonTop = 0;
buttonBottom = viewHeight;
rightButtonCenter.x = viewWidth- buttonRadius;
rightButtonCenter.y = viewHeight/2;
rightButtonLeft = rightButtonCenter.x- buttonRadius;
rightButtonRight = rightButtonCenter.x+ buttonRadius;
leftButtonCenter.x = buttonRadius;
leftButtonCenter.y = viewHeight/2;
leftButtonLeft = leftButtonCenter.x- buttonRadius;
leftButtonRight = leftButtonCenter.x+ buttonRadius;
}
private void drawLeftIcon(Canvas canvas){
path.reset();
path.moveTo(leftButtonCenter.x- buttonIconSize, leftButtonCenter.y);
path.lineTo(leftButtonCenter.x+ buttonIconSize, leftButtonCenter.y);
canvas.drawPath(path, buttonIconPaint);
canvas.save();
canvas.rotate(-buttonIconDegrees, leftButtonCenter.x, leftButtonCenter.y);
path.reset();
path.moveTo(leftButtonCenter.x, leftButtonCenter.y- buttonIconSize);
path.lineTo(leftButtonCenter.x, leftButtonCenter.y+ buttonIconSize);
canvas.drawPath(path, buttonIconPaint);
canvas.restore();
}
private void drawRightIcon(Canvas canvas){
path.reset();
path.moveTo(rightButtonCenter.x- buttonIconSize, rightButtonCenter.y);
path.lineTo(rightButtonCenter.x+ buttonIconSize, rightButtonCenter.y);
canvas.drawPath(path, buttonIconPaint);
canvas.save();
canvas.rotate(buttonIconDegrees, rightButtonCenter.x, rightButtonCenter.y);
path.reset();
path.moveTo(rightButtonCenter.x, rightButtonCenter.y- buttonIconSize);
path.lineTo(rightButtonCenter.x, rightButtonCenter.y+ buttonIconSize);
canvas.drawPath(path, buttonIconPaint);
canvas.restore();
}
}
新增attrs属性
<declare-styleable name="HorizontalExpandMenu">
//省略部分代码...
<attr name="button_style">
<enum name="right" value="0"/>
<enum name="left" value="1"/>
</attr>
<attr name="button_icon_size" format="dimension"></attr>
<attr name="button_icon_stroke_width" format="dimension"></attr>
<attr name="button_icon_color" format="color"></attr>
</declare-styleable>
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.anlia.expandmenu.widget.HorizontalExpandMenu
android:id="@+id/expandMenu1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp">
</com.anlia.expandmenu.widget.HorizontalExpandMenu>
<com.anlia.expandmenu.widget.HorizontalExpandMenu
android:id="@+id/expandMenu2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
app:button_style="left">
</com.anlia.expandmenu.widget.HorizontalExpandMenu>
</LinearLayout>
</RelativeLayout>
效果如图
设置按钮动画与点击事件
之前我们定义了
buttonIconDegrees
属性,下面我们通过
Animation的插值器
增减
buttonIconDegrees的数值
让按钮符号可以进行变换,同时
监听Touch为按钮设置点击事件
public class HorizontalExpandMenu extends RelativeLayout {
private boolean isExpand;
private float downX = -1;
private float downY = -1;
private int expandAnimTime;
private void init(){
buttonIconDegrees = 90;
expandAnimTime = typedArray.getInteger(R.styleable.HorizontalExpandMenu_expand_time,400);
isExpand = true;
anim = new ExpandMenuAnim();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break