Androidカスタム円形メニュー
28051 ワード
クールでスタイリッシュなUI効果は、開発者一人一人にとって非常に魅力的です.
上の図は雛形で、文字盤、回転盤、円形メニューに拡張できます.下図は拡張円形メニューです.録画ツールがはっきりしていないので、UIの特効効果は本当にいいです.興味があれば、後ろを見てください.もちろんインスピレーションは上図に由来します.
タイトルはカスタマイズされた丸いメニューで、私は主に丸いメニューの開発の流れを説明して、もし上の図に興味があるならば、伝言を残してください.コードは次の通りです.
ここを見て、多くの人が気絶したと信じています.じゃ、一緒に片付けましょう.
せっけい構想
1、汎用モード
上の図は画像に文字を加えて、もし私がボタンに変えたいならば、あるいは私は画像だけが必要です.ここではカスタマイズが必要です.どうすればいいのか、私はアダプティブモードを採用しました.みんなListViewの使い方を覚えています.私もここで参考にしました.
これによりMenuの高度なカスタマイズが可能になります.
2、構築メニュー項目
コードはbuildMenuItems()を参照し、mAdapterに対してサブViewを遍歴し、クリックイベントを追加し、addView()を呼び出してView Groupに追加し、このときシステムはonMeasure()を呼び出してサブViewに対してサイズを計算します.
3、itemサイズの計算
コードはmeasureMyself()とmeasureChildViews()を参照し、各itemのサイズを決定します.
4、itemレイアウト
まずitem(x,y)の円心からの長さを計算し、スケッチを描きました.
temp半径の3分の2に値を付けます.もちろん、満足のいく長さに変更することができます.
次に、(x,y)の座標を計算するには、次のようにします.
ここまで計算すると、(x,y)座標でメニュー項目のleftとtop位置を表すと、item全体が親コントロールに対して右下にずれていることがわかります.オフセットの問題を解決するために、itemコントロールの中心点を使用して、メニュー項目のleftとtop位置を表します.
最後にlayout()メソッドを呼び出し、itemの位置を決定します.
5、ジェスチャー回転
上には静的なMenuが完成していますが、シャドウ部分をスライドさせることでMenuを回転させるにはどうすればいいのでしょうか.
onTouchEvent()メソッドを書き換え、戻り値をtrueに変更する必要があります.ジェスチャーを処理して(ACTION_DOWN)を押し、(ACTION_UP)の状態を上げます.
まず,指の押下がシャドウの局所内にあるかどうかを判断する.注意指を押すのは指先がスクリーンに局所的に接触しているので、少しの誤差ではありません.
それから私たちは運動の速度を計算しなければなりません.私の最初の考えは重力で加速することです.同僚の賈さんに感謝しています.彼は私にもっと良い意見をくれました.速度=距離/時間です.
ACTION_DOWN:
ACTION_UP:
次に,指で押したxの座標と,xの座標を持ち上げることで,ユーザが左に滑るか,右に滑るかを判断する.
最後にhandlerによって各運動の角度を変え、Menuを自然に回転させた.
使用
1、xmlレイアウト
2、classファイル
ソースはgithubにアップロードしました.アドレスhttps://github.com/HpWens/ViewDemoと、改めてご注目ありがとうございました.
上の図は雛形で、文字盤、回転盤、円形メニューに拡張できます.下図は拡張円形メニューです.録画ツールがはっきりしていないので、UIの特効効果は本当にいいです.興味があれば、後ろを見てください.もちろんインスピレーションは上図に由来します.
タイトルはカスタマイズされた丸いメニューで、私は主に丸いメニューの開発の流れを説明して、もし上の図に興味があるならば、伝言を残してください.コードは次の通りです.
public class CircleMenuLayout extends ViewGroup {
//
private int mRadius;
//
private double mStartAngle = 0;
//padding 0
private float mPadding = 0;
// item
private int offsetRotation = 0;
//
private long lastTouchTime;
// false
boolean isRange = false;
// x,y
float x = 0, y = 0;
//
boolean isLeft = false;
//
private ListAdapter mAdapter;
// 0
private float speed = 0;
// item
private static final float ITEM_DIMENSION = 1 / 3f;
//
private static final int ROTATION_DEGREE = 3;
//distanceFromCenter Item
private static final float DISTANCE_FROM_CENTER = 2 / 3f;
//speed attenuation
private static final int SPEED_ATTENUATION = 1;
//
private static final int ANGLE = 6;
//
private static final int EMPTY_MESSAGE = 1;
// MenuItem
private OnItemClickListener mOnMenuItemClickListener;
// item
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == EMPTY_MESSAGE) {
if (speed > 0) {
if (isLeft) {
//
offsetRotation -= ANGLE;
} else {
offsetRotation += ANGLE;
}
//
speed -= SPEED_ATTENUATION;
invalidate();
handler.sendEmptyMessageDelayed(EMPTY_MESSAGE, 50);
}
}
}
};
/** * @param context * @param attrs */
public CircleMenuLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setPadding(0, 0, 0, 0);
}
/** * @param context */
public CircleMenuLayout(Context context) {
super(context);
setPadding(0, 0, 0, 0);
}
public void setAdapter(ListAdapter mAdapter) {
this.mAdapter = mAdapter;
}
//
private void buildMenuItems() {
// , menu item
for (int i = 0; i < mAdapter.getCount(); i++) {
final View itemView = mAdapter.getView(i, null, this);
final int position = i;
if (itemView != null) {
itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mOnMenuItemClickListener != null) {
mOnMenuItemClickListener.onItemClickListener(itemView, position);
}
}
});
}
// view
addView(itemView);
}
}
//
@Override
protected void onAttachedToWindow() {
if (mAdapter != null) {
buildMenuItems();
}
super.onAttachedToWindow();
}
// , menu item
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//
measureMyself(widthMeasureSpec, heightMeasureSpec);
//
measureChildViews();
}
private void measureMyself(int widthMeasureSpec, int heightMeasureSpec) {
int resWidth = 0;
int resHeight = 0;
// ,
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//
if (widthMode != MeasureSpec.EXACTLY
|| heightMode != MeasureSpec.EXACTLY) {
//
resWidth = getSuggestedMinimumWidth();
// ,
resWidth = resWidth == 0 ? getDefaultWidth() : resWidth;
resHeight = getSuggestedMinimumHeight();
// ,
resHeight = resHeight == 0 ? getDefaultWidth() : resHeight;
} else {
// , ;
resWidth = resHeight = Math.min(width, height);
}
setMeasuredDimension(resWidth, resHeight);
}
private void measureChildViews() {
//
mRadius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2;
// menu item
final int count = getChildCount();
// menu item
int childSize = (int) (mRadius * ITEM_DIMENSION);
// menu item
int childMode = MeasureSpec.EXACTLY;
//
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
// menu item ; , item
int makeMeasureSpec = -1;
makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize,
childMode);
child.measure(makeMeasureSpec, makeMeasureSpec);
}
}
// menu item
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
refresh();
}
// item
public void refresh() {
final int childCount = getChildCount();
// menu item , item
float angleDelay = 360 / childCount;
//
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
int x = (int) Math.round(Math.sin(Math.toRadians(angleDelay * (i + 1) - offsetRotation)) * (mRadius * DISTANCE_FROM_CENTER));
int y = (int) Math.round(Math.cos(Math.toRadians(angleDelay * (i + 1) - offsetRotation)) * (mRadius * DISTANCE_FROM_CENTER));
// item
if (x <= 0 && y >= 0) {
x = mRadius - Math.abs(x);
y = mRadius - y;
} else if (x <= 0 && y <= 0) {
y = mRadius + Math.abs(y);
x = mRadius - Math.abs(x);
} else if (x >= 0 && y <= 0) {
y = mRadius + Math.abs(y);
x = mRadius + x;
} else if (x >= 0 && y >= 0) {
x = mRadius + x;
y = mRadius - Math.abs(y);
}
// item
x = x - (int) (mRadius * ITEM_DIMENSION) / 2;
y = y - (int) (mRadius * ITEM_DIMENSION) / 2;
// child view
child.layout(x, y,
x + (int) (mRadius * ITEM_DIMENSION), y + (int) (mRadius * ITEM_DIMENSION));
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//
x = event.getX();
y = event.getY();
//
int error = 10;
if ((x - mRadius) * (x - mRadius) + (y - mRadius) * (y - mRadius) < (mRadius + error) * (mRadius + error)) {
isRange = true;
lastTouchTime = System.currentTimeMillis();
} else {
isRange = false;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
float x1 = event.getX();
float y1 = event.getY();
if (isRange) {
long timeStamp = System.currentTimeMillis() - lastTouchTime;
float distance = (float) Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
float speed = distance / timeStamp;
if (x1 - x > 0) {
isLeft = false;
} else {
isLeft = true;
}
//
speed(speed);
}
break;
}
return true;
}
public void speed(float speed) {
this.speed = speed * ROTATION_DEGREE;
handler.sendEmptyMessage(EMPTY_MESSAGE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.parseColor("#dddddd"));
canvas.drawCircle(mRadius, mRadius, mRadius, paint);
refresh();
}
//
public interface OnItemClickListener {
public void onItemClickListener(View v, int position);
}
// MenuItem
public void setOnItemClickListener(OnItemClickListener listener) {
this.mOnMenuItemClickListener = listener;
}
/** * layout * * @return */
private int getDefaultWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return Math.min(outMetrics.widthPixels, outMetrics.heightPixels);
}
}
ここを見て、多くの人が気絶したと信じています.じゃ、一緒に片付けましょう.
せっけい構想
1、汎用モード
上の図は画像に文字を加えて、もし私がボタンに変えたいならば、あるいは私は画像だけが必要です.ここではカスタマイズが必要です.どうすればいいのか、私はアダプティブモードを採用しました.みんなListViewの使い方を覚えています.私もここで参考にしました.
public void setAdapter(ListAdapter mAdapter) {
this.mAdapter = mAdapter;
}
これによりMenuの高度なカスタマイズが可能になります.
2、構築メニュー項目
コードはbuildMenuItems()を参照し、mAdapterに対してサブViewを遍歴し、クリックイベントを追加し、addView()を呼び出してView Groupに追加し、このときシステムはonMeasure()を呼び出してサブViewに対してサイズを計算します.
3、itemサイズの計算
コードはmeasureMyself()とmeasureChildViews()を参照し、各itemのサイズを決定します.
4、itemレイアウト
まずitem(x,y)の円心からの長さを計算し、スケッチを描きました.
int x = (int) Math.round(Math.sin(Math.toRadians(a)) * temp);
int y = (int) Math.round(Math.cos(Math.toRadians(a)) * temp);
temp半径の3分の2に値を付けます.もちろん、満足のいく長さに変更することができます.
次に、(x,y)の座標を計算するには、次のようにします.
if (x <= 0 && y >= 0) { //
x = mRadius - Math.abs(x);
y = mRadius - y;
} else if (x <= 0 && y <= 0) {//
y = mRadius + Math.abs(y);
x = mRadius - Math.abs(x);
} else if (x >= 0 && y <= 0) {//
y = mRadius + Math.abs(y);
x = mRadius + x;
} else if (x >= 0 && y >= 0) {//
x = mRadius + x;
y = mRadius - Math.abs(y);
}
ここまで計算すると、(x,y)座標でメニュー項目のleftとtop位置を表すと、item全体が親コントロールに対して右下にずれていることがわかります.オフセットの問題を解決するために、itemコントロールの中心点を使用して、メニュー項目のleftとtop位置を表します.
x = x - item / 2;
y = y - item / 2;
最後にlayout()メソッドを呼び出し、itemの位置を決定します.
5、ジェスチャー回転
上には静的なMenuが完成していますが、シャドウ部分をスライドさせることでMenuを回転させるにはどうすればいいのでしょうか.
onTouchEvent()メソッドを書き換え、戻り値をtrueに変更する必要があります.ジェスチャーを処理して(ACTION_DOWN)を押し、(ACTION_UP)の状態を上げます.
まず,指の押下がシャドウの局所内にあるかどうかを判断する.注意指を押すのは指先がスクリーンに局所的に接触しているので、少しの誤差ではありません.
x = event.getX();
y = event.getY();
if ((x - x) * (x - x) + (y - y) * (y - y) < ( x+ ) * ( y+ )) {
isRange = true;
}
それから私たちは運動の速度を計算しなければなりません.私の最初の考えは重力で加速することです.同僚の賈さんに感謝しています.彼は私にもっと良い意見をくれました.速度=距離/時間です.
ACTION_DOWN:
lastTouchTime = System.currentTimeMillis();
ACTION_UP:
long timeStamp = System.currentTimeMillis() - lastTouchTime;
float distance = (float) Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
float speed = distance / timeStamp;
次に,指で押したxの座標と,xの座標を持ち上げることで,ユーザが左に滑るか,右に滑るかを判断する.
if (x1 - x > 0) {
isLeft = false;
} else {
isLeft = true;
}
最後にhandlerによって各運動の角度を変え、Menuを自然に回転させた.
if (isLeft) {
//
offsetRotation -= ANGLE;
} else {
//
offsetRotation += ANGLE;
}
//
speed -= SPEED_ATTENUATION;
invalidate();//
handler.sendEmptyMessageDelayed(EMPTY_MESSAGE, 50);
使用
1、xmlレイアウト
<com.github.ws.viewdemo.widget.CircleMenuLayout
android:id="@+id/cm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f0f0f0">
</com.github.ws.viewdemo.widget.CircleMenuLayout>
2、classファイル
circleMenuLayout.setAdapter(new MyAdapter());
circleMenuLayout.setOnItemClickListener(new CircleMenuLayout.OnItemClickListener() {
@Override
public void onItemClickListener(View v, int position) {
Toast.makeText(MainActivity.this, mList.get(position).text + "", Toast.LENGTH_SHORT).show();
}
});
ソースはgithubにアップロードしました.アドレスhttps://github.com/HpWens/ViewDemoと、改めてご注目ありがとうございました.