最开始看到FloatView就想,为啥使用float.。原来Float 有浮动,漂浮的意思- -。
一、FloatView的功能
首先效果图奉献上:
功能特点:
1. 可以设置menu的弹出方向
2. 可以代码控制添加,删除子类botton
3. 可根据developer的需求,更改样式
涉及到的内容:
1. 自定义控件的流程
2. 子类view的排列运算
3. 样式的设计
4. 动画的实现
二、主要代码详解
1. FloatingActionsMenu
思路:
FloatingActionsMenu 继承viewgroup, 因为他需要包含一些botton用来展示。
重写方法:
3个构造方法:对样式进行初始化
onMeasure : 计算自身view的大小
onLayout : 对其内部的view的排列
animation :动画的添加
buttonCount::统计内部view的数量
(1) 样式设计
这边设计的样式,主要用来对
第一个FloatingActionButton样式的设计、
文字的方向、
图标展开的方向。
<declare-styleable name="FloatingActionsMenu">
<attr name="fab_addButtonColorPressed" format="color"/>
<attr name="fab_addButtonColorNormal" format="color"/>
<attr name="fab_addButtonSize" format="enum"> // 调节第一个botton的大小
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
<enum name="large" value="2"/>
</attr>
<attr name="fab_addButtonPlusIconColor" format="color"/>
<attr name="fab_addButtonStrokeVisible" format="boolean"/>
<attr name="fab_labelStyle" format="reference"/>
<attr name="fab_labelsPosition" format="enum"> //文字描述的方向
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
<attr name="fab_expandDirection" format="enum"> //展示图标的方向
<enum name="up" value="0"/>
<enum name="down" value="1"/>
<enum name="left" value="2"/>
<enum name="right" value="3"/>
</attr>
</declare-styleable>
(2) 初始化
通过TypeArray 将定义的styleable 中的参数提取出来,如果xml文件中没有设值,则取默认值
注意:需将attr进行释放。
//初始化属性值
private void init(Context context, AttributeSet attributeSet) {
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset));
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
mTouchDelegateGroup = new TouchDelegateGroup(this);
setTouchDelegate(mTouchDelegateGroup);
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor, getColor(android.R.color.white));
mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal, getColor(R.color.float_menu_default_color));
mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed, getColor(R.color.float_menu_default_pressed_color));
mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL);
mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true);
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
attr.recycle();
// if (mLabelsStyle != 0 && expandsHorizontally()) {
// throw new IllegalStateException("Action labels in horizontal expand orientation is not supported.");
// }
//创建一个空ImageView,并且给他一个点击事件,用力展示,隐藏child
createAddButton(context);
}
初始化过程中,会创建第一个ImageBotton,用来对Menu进行一个展示和关闭功能。
通过addView ,可将该botton添加到view中,并且通过mButtonsCount统计子类view的数量。
//创建一个空ImageView,并且给他一个点击事件,用力展示,隐藏child
private void createAddButton(Context context) {
mAddButton = new FloatingActionButton(context) {
@Override
void updateBackground() {
mColorNormal = mAddButtonColorNormal;
mColorPressed = mAddButtonColorPressed;
super.updateBackground();
}
};
mAddButton.setId(R.id.fab_expand_menu_button); //设置id
Drawable d = getResources().getDrawable(R.drawable.ic_arrow_white); //设置icon资源
mAddButton.setIconDrawable(d);
mAddButton.setSize(mAddButtonSize); //设置button的大小
mAddButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggle(); //打开,关闭菜单
}
});
addView(mAddButton, super.generateDefaultLayoutParams());
mButtonsCount++;
}
(3)onMeasure
思路:通过getChildAt(i) 获得内部vew的宽度以及高度(不断叠加),从而计算出正确的合适高度。
//计算自身view的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
mMaxButtonWidth = 0;
mMaxButtonHeight = 0;
int maxLabelWidth = 0;
for (int i = 0; i < mButtonsCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) { //如果内部view有隐藏的,直接return,不参加计算
continue;
}
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth()); //计算宽度
height += child.getMeasuredHeight(); //计算高度
break;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += child.getMeasuredWidth();
mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
break;
}
if (!expandsHorizontally()) {
TextView label = (TextView) child.getTag(R.id.fab_label);
if (label != null) {
maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
}
}
}
if (!expandsHorizontally()) {
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
} else {
height = mMaxButtonHeight;
}
switch (mExpandDirection) { //将间隔添加上去
case EXPAND_UP:
case EXPAND_DOWN:
height += mButtonSpacing * (mButtonsCount - 1);
height = adjustForOvershoot(height);
break;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += mButtonSpacing * (mButtonsCount - 1);
width = adjustForOvershoot(width);
break;
}
setMeasuredDimension(width, height); //设置view的大小
}
(4) onLayout
这边挑一个图标的位置
case EXPAND_RIGHT:
boolean expandLeft = mExpandDirection == EXPAND_LEFT;
int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0;
// Ensure mAddButton is centered on the line where the buttons should be
int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2; //第一个button的位置
mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight());
int nextX = expandLeft ?
addButtonX - mButtonSpacing :
addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing;
//根据getChildAt 来确定child位置
for (int i = mButtonsCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child == mAddButton || child.getVisibility() == GONE) continue;
int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX;
int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2;
child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());//通过第一个button的位置以及自身的大小确定位置
float collapsedTranslation = addButtonX - childX;
float expandedTranslation = 0f;
child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation);
child.setAlpha(mExpanded ? 1f : 0f);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
params.setAnimationsTarget(child);
View label = (View) child.getTag(R.id.fab_label);
if(label != null) {
label.setVisibility(GONE);
}
nextX = expandLeft ?
childX - mButtonSpacing :
childX + child.getMeasuredWidth() + mButtonSpacing;
}
break;
}
(5) 展开与闭合的操作
private void collapse(boolean immediately) {
if (mExpanded) {
mExpanded = false;
mTouchDelegateGroup.setEnabled(false);
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
mCollapseAnimation.start(); //animation的改动
mExpandAnimation.cancel();
Drawable d = getResources().getDrawable(R.drawable.ic_arrow_white);
mAddButton.setIconDrawable(d); //改变第一个button的样式
setAddButtonColor(getResources().getColor(R.color.float_menu_default_color), getResources().getColor(R.color.float_menu_default_pressed_color));
mAddButton.updateBackground();
if (mListener != null) {
mListener.onMenuCollapsed();
}
}
}
public void toggle() {
if (mExpanded) {
collapse();
} else {
expand();
}
}
//展示 并且改变第一个view的样式
public void expand() {
if (!mExpanded) {
mExpanded = true;
mTouchDelegateGroup.setEnabled(true);
mCollapseAnimation.cancel();
mExpandAnimation.start();
Drawable d = getResources().getDrawable(R.drawable.ic_close_black);
mAddButton.setIconDrawable(d); //改变第一个button的样式
setAddButtonColor(getResources().getColor(R.color.white), getResources().getColor(R.color.float_menu_exposed_pressed_color));
mAddButton.updateBackground();
if (mListener != null) {
mListener.onMenuExpanded();
}
}
}
(6) 其他设置
1. 提供了addButton 以及 removeButton方法
2. onSaveInstanceState 保存了当前状态
3. setAddButtonColor 设置第一个button的颜色
4. 使用了TouchDelegate 来扩大点击区域
三、源码下载
https://github.com/stormxz/FloatViewT