最近项目上有一个关于悬浮组件需求需要优化,想好好多方法,一开始是“大块头”悬浮在顶部——位置占据过大,导致显示内容较少;接着是在向下滑动时隐藏部分控件,向上滑动时再做显示,但是又出现闪跳,而且还是占据较大控件;最后想到可以做仿PathButton控件——控件占据小,体验也不错...
好了废话不多说了,希望接下来的内容对需要该控件的你有帮助(可以自定义控件显示位置)
一、首先创建一个类ComposerLayout去继承RelativeLayout,并创建一个初始化的方法:(包括主按钮和子按钮的添加——图片、位置、大小)
/**
* 初始化
*
* @param imgResId 子按鈕drawable的图片id容器
* @param mainButtonBgResId 主按鈕drawable的图片id
* @param mainCrossResId 主按鈕上面转动十字drawable的图片id
* @param pCode 控件显示位置代码,例如“右上角”係ALIGN_PARENT_BOTTOM|ALIGN_PARENT_RIGHT
* @param radius 半径
* @param buttonRadius 转主、子按钮半径
* @param durationMillis 动画时间
*/
public void init(int[] imgResId, int mainButtonBgResId, int mainCrossResId, byte pCode, int radius, int buttonRadius, final int durationMillis) {
durationTime = durationMillis;
// 處理pCode,將自定義嘅位置值改成align值
int align1 = 12, align2 = 14;
if (pCode == RIGHTBOTTOM) { // 右下角
align1 = ALIGN_PARENT_RIGHT;
align2 = ALIGN_PARENT_BOTTOM;
} else if (pCode == CENTERBOTTOM) {// 中下
align1 = CENTER_HORIZONTAL;
align2 = ALIGN_PARENT_BOTTOM;
} else if (pCode == LEFTBOTTOM) { // 左下角
align1 = ALIGN_PARENT_LEFT;
align2 = ALIGN_PARENT_BOTTOM;
} else if (pCode == LEFTCENTER) { // 左中
align1 = ALIGN_PARENT_LEFT;
align2 = CENTER_VERTICAL;
} else if (pCode == LEFTTOP) { // 左上角
align1 = ALIGN_PARENT_LEFT;
align2 = ALIGN_PARENT_TOP;
} else if (pCode == CENTERTOP) { // 中上
align1 = CENTER_HORIZONTAL;
align2 = ALIGN_PARENT_TOP;
} else if (pCode == RIGHTTOP) { // 右上角
align1 = ALIGN_PARENT_RIGHT;
align2 = ALIGN_PARENT_TOP;
} else if (pCode == RIGHTCENTER) { // 右中
align1 = ALIGN_PARENT_RIGHT;
align2 = CENTER_VERTICAL;
}
// 如果細過半徑就整大佢
RelativeLayout.LayoutParams thislps = (LayoutParams) this.getLayoutParams();
Bitmap mBottom = BitmapFactory.decodeResource(mContext.getResources(), imgResId[0]);
if (pCode == CENTERBOTTOM || pCode == CENTERTOP) {
if (thislps.width != -1 && thislps.width != -2 && thislps.width < (radius + mBottom.getWidth() + radius * 0.1) * 2) {
thislps.width = (int) ((radius * 1.1 + mBottom.getWidth()) * 2);
}
} else {
if (thislps.width != -1 && thislps.width != -2 && thislps.width < radius + mBottom.getWidth() + radius * 0.1) {
// -1係FILL_PARENT,-2係WRAP_CONTENT
thislps.width = (int) (radius * 1.1 + mBottom.getWidth());
}
}
if (pCode == LEFTCENTER || pCode == RIGHTCENTER) {
if (thislps.height != -1 && thislps.height != -2 && thislps.height < (radius + mBottom.getHeight() + radius * 0.1) * 2) {
thislps.width = (int) ((radius * 1.1 + mBottom.getHeight()) * 2);
}
} else {
if (thislps.height != -1 && thislps.height != -2 && thislps.height < radius + mBottom.getHeight() + radius * 0.1) {
thislps.height = (int) (radius * 1.1 + mBottom.getHeight());
}
}
this.setLayoutParams(thislps);
// 兩個主要層
RelativeLayout subLayout = new RelativeLayout(mContext);// 包含若干子按鈕嘅層
RelativeLayout mainLayout = new RelativeLayout(mContext); // 主按扭
subButtonContainer = new LinearLayout[imgResId.length];
// N個子按鈕
for (int i = 0; i < imgResId.length; i++) {
ImageView img = new ImageView(mContext);// 子按扭圖片
img.setImageResource(imgResId[i]);
LinearLayout.LayoutParams subLayoutLps = new LinearLayout.LayoutParams(buttonRadius, buttonRadius);
img.setLayoutParams(subLayoutLps);
subButtonContainer[i] = new LinearLayout(mContext);// 子按钮层
subButtonContainer[i].setId(100 + i);// 子按钮id(随意设置)
subButtonContainer[i].addView(img);
RelativeLayout.LayoutParams subLayoutRps = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
subLayoutRps.alignWithParent = true;
subLayoutRps.addRule(align1, RelativeLayout.TRUE);
subLayoutRps.addRule(align2, RelativeLayout.TRUE);
subButtonContainer[i].setLayoutParams(subLayoutRps);
subButtonContainer[i].setVisibility(View.INVISIBLE);// 此处不能为GONE
subLayout.addView(subButtonContainer[i]);
}
RelativeLayout.LayoutParams subLayoutLps = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
subLayoutLps.alignWithParent = true;
subLayoutLps.addRule(align1, RelativeLayout.TRUE);
subLayoutLps.addRule(align2, RelativeLayout.TRUE);
subLayout.setLayoutParams(subLayoutLps);
RelativeLayout.LayoutParams mainLayoutLps = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mainLayoutLps.alignWithParent = true;
mainLayoutLps.addRule(align1, RelativeLayout.TRUE);
mainLayoutLps.addRule(align2, RelativeLayout.TRUE);
mainLayout.setLayoutParams(mainLayoutLps);
mainLayout.setBackgroundResource(mainButtonBgResId);
cross = new ImageView(mContext);
cross.setImageResource(mainCrossResId);
RelativeLayout.LayoutParams mainCrossLps = new RelativeLayout.LayoutParams(buttonRadius, buttonRadius);
mainCrossLps.alignWithParent = true;
mainCrossLps.addRule(CENTER_IN_PARENT, RelativeLayout.TRUE);
cross.setLayoutParams(mainCrossLps);
mainLayout.addView(cross);
mAnimationsUtils = new MyAnimationsUtils(subLayout, pCode, radius);
mainLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (areButtonsShowing) {
collapse();
} else {
expand();
}
}
});
this.addView(subLayout);
this.addView(mainLayout);
hasInit = true;
}
二、接着创建动画类MyAnimationsUtils,这边主要是位置的计算:
1、如果位置设在左中或者右中,其子空间位置计算公式为(startAngle为初始角度):
deltaX = Math.sin((offAngle * i + startAngle) * Math.PI / 180) * Radius;
deltaY = Math.cos((offAngle * i + startAngle) * Math.PI / 180) * Radius;
其他位置都是一样的:
deltaY = Math.sin((offAngle * i + startAngle) * Math.PI / 180) * Radius;
deltaX = Math.cos((offAngle * i + startAngle) * Math.PI / 180) * Radius;
2、主按钮的自转动画:
/**
* MainButton自轉函數(原本就有嘅靜態函數,未實體化都可以調用)
*
* @param fromDegrees 從幾多度
* @param toDegrees 到幾多度
* @param durationMillis 轉幾耐
*/
public static Animation getRotateAnimation(float fromDegrees, float toDegrees, int durationMillis) {
RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
return rotate;
}
三、最后
1、在布局中使用:
<RelativeLayout
android:id="@+id/rlParent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 长宽可以自定义,但是不能过小,会显示不出来 -->
<!-- align可以自定义,控件显示位置 -->
<com.mymvp.pathbutton.ComposerLayout
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_centerVertical="true"
android:padding="8dp"/>
</RelativeLayout>
2、在MainActivity中使用:
a、初始化:
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
// 引用控件
mComposerLayout = (ComposerLayout) findViewById(R.id.test);
mComposerLayout.init(new int[]{R.drawable.composer_camera, R.drawable.composer_music, R.drawable
.composer_place, R.drawable.composer_sleep, R.drawable.composer_thought, R.drawable.composer_with}, R
.drawable.composer_button, R.drawable.composer_icn_plus, ComposerLayout.CENTERBOTTOM, (int) (width /
4.8), width / 9, 300);
b、添加子按钮点击事件:
OnClickListener onClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String drawableName = "";
if (v.getId() == 100 + 0) {
drawableName = "composer_camera";
} else if (v.getId() == 100 + 1) {
drawableName = "composer_music";
} else if (v.getId() == 100 + 2) {
drawableName = "composer_place";
} else if (v.getId() == 100 + 3) {
drawableName = "composer_sleep";
} else if (v.getId() == 100 + 4) {
drawableName = "composer_thought";
} else if (v.getId() == 100 + 5) {
drawableName = "composer_with";
}
Toast.makeText(MainActivity.this, "clickIDrawableName = " + drawableName + ", clickId = " + v.getId() , Toast.LENGTH_SHORT).show();
}
};
mComposerLayout.setButtonsOnClickListener(onClickListener);
ok!
[源码下载地址] 访问密码 cccb