最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android自定義實現(xiàn)轉(zhuǎn)盤菜單

 更新時間:2023年12月01日 10:10:21   作者:時光少年  
旋轉(zhuǎn)菜單是一種占用空間較大,實用性稍弱的UI,本文主要為大家詳細介紹了Android如何自定義實現(xiàn)轉(zhuǎn)盤菜單,文中的示例代碼講解詳細,有需要的小伙伴可以參考下

一、前言

旋轉(zhuǎn)菜單是一種占用空間較大,實用性稍弱的UI,一方面由于展示空間的問題,其展示的數(shù)據(jù)有限,但另一方面真由于這個原因,對用戶而言趣味性和操作性反而更有好。

二、繪制原理

繪制原理很簡單,通過細微的觀察,我們發(fā)現(xiàn)文字是不需要旋轉(zhuǎn)的,也就是每個菜單是不需要自旋轉(zhuǎn),只需要旋轉(zhuǎn)其位置坐標即可,實際上其難點并不是繪制,而是在于觸摸事件的處理方式。

本篇菜單特性:

  • 動態(tài)設(shè)置菜單
  • 計算旋轉(zhuǎn)方向和旋轉(zhuǎn)角度
  • 支持點擊

難點1:

旋轉(zhuǎn)方向判斷,旋轉(zhuǎn)時記錄起始點,計算出旋轉(zhuǎn)方向。

首先,我們要理解,Touch事件也存在抽象的坐標體系,和View左上角重合,因此我們需要轉(zhuǎn)換坐標

float cx = event.getX() - getWidth() / 2F;
float cy = event.getY() - getHeight() / 2F;

旋轉(zhuǎn)角度的計算

這種計算是為了計算出與原始落點位置的夾角,這里的方法是計算使用Math.asin反正切函數(shù),然后結(jié)合坐標系進行判斷

float lineWidth = (float) Math.sqrt(Math.pow(cx, 2) + Math.pow(cy, 2));
float degreeRadian = (float) Math.asin(cy / lineWidth);
float dr = 0;
if (cy > 0) {
         //一二象限
 if (cx > 0) {
    dr = degreeRadian;
 } else {
   dr = (float) ((Math.PI - degreeRadian));
 }

} else {
    //三四象限
    if (cx > 0) {
         dr = (float) (Math.PI * 2 - Math.abs(degreeRadian));
    } else {
        dr = (float) ((Math.PI + Math.abs(degreeRadian)));
    }
}

由于對Math的了解我們知道,Math.asin不能反映真實的夾角,因此需要做上面的補充。但是后來我們發(fā)現(xiàn),Math.atan2函數(shù)的存在,直接可以求出斜率夾角,而且不會丟失象限關(guān)系,一下子就省了好幾行代碼。

dr = (float) Math.atan2(cy, cx);

難點2:實時更新

為了旋轉(zhuǎn),我們可能忘記記錄最新位置,這個可能導(dǎo)致圓反向旋轉(zhuǎn),因此要實時記錄位置

eStartX = cx;
eStartY = cy;

難點3:由于攔截了UP事件,因此需要對UP事件進行專門處理

if (System.currentTimeMillis() - startDownTime > 500) {    
  break;
}
float upX = event.getX() - getWidth() / 2F;
float upY = event.getY() - getHeight() / 2F;
handleClickTap(upX, upY);

全部代碼:

public class OribitView extends View {

    private final String TAG = "OribitView";
    private DisplayMetrics displayMetrics;
    private float mOutlineRaduis;
    private float mInlineRadius;
    private TextPaint mPaint;
    private float lineWidth = 5f;
    private float textSize = 12f;
    private int itemCount = 5;

    private int mTouchSlop = 0;
    private float rotateDegreeRadian = 0;

    private OnItemClickListener onItemClickListener;

    private float eStartX = 0f;
    private float eStartY = 0f;
    private boolean isMoveTouch = false;
    private float startDegreeRadian = 0l; //記錄用于落點角度,用于參考
    private long startDownTime = 0l;

    Rect bounds = new Rect();
    private final List<OribitItemPoint> mOribitItemPoints = new ArrayList<>();

    public OribitView(Context context) {
        this(context, null);
    }

    public OribitView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OribitView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        displayMetrics = context.getResources().getDisplayMetrics();
        initPaint();
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        setLayerType(LAYER_TYPE_SOFTWARE,null);
    }

    private void initPaint() {
        // 實例化畫筆并打開抗鋸齒
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(dpToPx(textSize));

    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = displayMetrics.widthPixels / 2;
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = displayMetrics.widthPixels / 2;
        }
        widthSize = heightSize = Math.min(widthSize, heightSize);

        setMeasuredDimension(widthSize, heightSize);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mOutlineRaduis = w / 2.0f - dpToPx(lineWidth);
        mInlineRadius = mOutlineRaduis * 3 / 5.0f - dpToPx(lineWidth);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = getWidth();
        int height = getWidth();

        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(dpToPx(lineWidth / 4));
        mPaint.setColor(Color.GRAY);
        int id = canvas.save();

        float centerRadius = (mOutlineRaduis + mInlineRadius) / 2;
        float itemRadius = (mOutlineRaduis - mInlineRadius) / 2;

        canvas.translate(width / 2F, height / 2F);

      //  canvas.drawCircle(0, 0, mOutlineRaduis, mPaint); //畫外框
      //  canvas.drawCircle(0, 0, mInlineRadius, mPaint); //畫內(nèi)框

        float strokeWidth = mPaint.getStrokeWidth();
        mPaint.setStrokeWidth(itemRadius * 2 - dpToPx(lineWidth / 2));
        mPaint.setColor(Color.DKGRAY);
        mPaint.setShadowLayer(10,0,10,Color.DKGRAY);
        canvas.drawCircle(0, 0, centerRadius, mPaint);
        mPaint.setStrokeWidth(strokeWidth);

        float degree = (float) (2 * Math.asin(itemRadius / centerRadius));
        //計算出從原點過item的切線夾角,求出每個圓所占夾角大小

        float spaceDegree = (float) ((Math.PI * 2 - degree * itemCount) / itemCount);


        for (int i = 0; i < mOribitItemPoints.size(); i++) {

            OribitItemPoint itemPoint = mOribitItemPoints.get(i);

            float x = (float) (centerRadius * Math.cos(rotateDegreeRadian + i * (spaceDegree + degree)));
            float y = (float) (centerRadius * Math.sin(rotateDegreeRadian + i * (spaceDegree + degree)));

            itemPoint.x = x;
            itemPoint.y = y;


            OribitItem oribitItem = itemPoint.getOribitItem();

            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(oribitItem.backgroundColor);


            //減去線寬
            float strokeOffset = dpToPx(lineWidth / 2);

            canvas.drawCircle(x, y, itemRadius - strokeOffset, mPaint);
            mPaint.setColor(oribitItem.textColor);
            String text = String.valueOf(oribitItem.text);
            mPaint.getTextBounds(text, 0, text.length(), bounds);
            float textBaseline = getTextPaintBaseline(mPaint) - y - bounds.height() + strokeOffset;
            canvas.drawText(text, x - bounds.width() / 2F, -textBaseline, mPaint);

        }
        canvas.restoreToCount(id);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                eStartX = event.getX() - getWidth() / 2F;
                //這里轉(zhuǎn)為原點為畫布中心的點,便于計算角度
                eStartY = event.getY() - getHeight() / 2F;

                //求出落點與坐標系x軸方向的夾角(
                float locationRadian = (float) Math.asin(eStartY / (float) Math.sqrt(Math.pow(eStartX, 2) + Math.pow(eStartY, 2)));

//                //根據(jù)正弦值計算起點在那個象限
//                if (eStartY > 0) {
//                    //一二象限
//                    if (eStartX < 0) {
//                        startDegreeRadian = (float) (Math.PI - locationRadian);
//                    } else {
//                        startDegreeRadian = locationRadian;
//                    }
//                } else {
//                    //三四象限
//                    if (eStartX > 0) {
//                        startDegreeRadian = (float) (Math.PI * 2 - Math.abs(locationRadian));
//                    } else {
//                        startDegreeRadian = (float) (Math.PI + Math.abs(locationRadian));
//                    }
//                }
                startDegreeRadian = locationRadian;
                startDownTime = System.currentTimeMillis();
                getParent().requestDisallowInterceptTouchEvent(true);
                super.onTouchEvent(event);
                return true;
            case MotionEvent.ACTION_MOVE:
                //坐標轉(zhuǎn)換
                float cx = event.getX() - getWidth() / 2F;
                float cy = event.getY() - getHeight() / 2F;

                float dx = cx - eStartX;
                float dy = cy - eStartY;
                float slideSlop = (float) Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));

                if (slideSlop > mTouchSlop) {
                    isMoveTouch = true;
                } else {
                    isMoveTouch = false;

                }
                if (isMoveTouch) {

                    float lineWidth = (float) Math.sqrt(Math.pow(cx, 2) + Math.pow(cy, 2));
                    float degreeRadian = (float) Math.asin(cy / lineWidth);

                    float dr = 0;
//
//                    if (cy > 0) {
//                        //一二象限
//                        if (cx > 0) {
//                            dr = degreeRadian;
//                        } else {
//                            dr = (float) ((Math.PI - degreeRadian));
//                        }
//
//                    } else {
//                        //三四象限
//                        if (cx > 0) {
//                            dr = (float) (Math.PI * 2 - Math.abs(degreeRadian));
//                        } else {
//                            dr = (float) ((Math.PI + Math.abs(degreeRadian)));
//                        }
//                    }

                    dr = (float) Math.atan2(cy, cx);
                    rotateDegreeRadian += (dr - startDegreeRadian);
                    startDegreeRadian = dr;

                    eStartX = cx;
                    eStartY = cy;

                    postInvalidate();
                }

                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:
                getParent().requestDisallowInterceptTouchEvent(false);
                if (isMoveTouch) {
                    isMoveTouch = false;
                    break;
                }

                if (System.currentTimeMillis() - startDownTime > 500) {
                    break;
                }
                float upX = event.getX() - getWidth() / 2F;
                float upY = event.getY() - getHeight() / 2F;
                handleClickTap(upX, upY);

                break;
        }

        return super.onTouchEvent(event);
    }

    private void handleClickTap(float upX, float upY) {
        if (itemCount == 0 || mOribitItemPoints == null) return;

        OribitItemPoint clickItemPoint = null;
        float itemRadius = (mOutlineRaduis - mInlineRadius) / 2;

        for (OribitItemPoint itemPoint : mOribitItemPoints) {
            if (Float.isNaN(itemPoint.x) || Float.isNaN(itemPoint.y)) {
                continue;
            }
            float dx = (itemPoint.x - upX);
            float dy = (itemPoint.y - upY);
            float clickSlop = (float) Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
            if (clickSlop >= itemRadius) {
                continue;
            }
            clickItemPoint = itemPoint;
            break;
        }

        if (clickItemPoint == null) return;

        if (this.mOribitItemPoints != null) {
            this.onItemClickListener.onItemClick(this, clickItemPoint.oribitItem);
        }

    }

    public int getItemCount() {
        return itemCount;
    }

    public static float getTextPaintBaseline(Paint p) {
        Paint.FontMetrics fontMetrics = p.getFontMetrics();
        return (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
    }

    public void showItems(List<OribitItem> oribitItems) {

        mOribitItemPoints.clear();
        if (oribitItems != null) {
            for (OribitItem item : oribitItems) {
                OribitItemPoint point = new OribitItemPoint();
                point.x = Float.NaN;
                point.y = Float.NaN;
                point.oribitItem = item;
                mOribitItemPoints.add(point);
            }
        }
        this.itemCount = mOribitItemPoints.size();
        postInvalidate();
    }


    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public static class OribitItem {
        public String text;
        public int textColor;
        public int backgroundColor;

    }

    static class OribitItemPoint<T extends OribitItem> extends PointF {

        private T oribitItem;

        public void setOribitItem(T oribitItem) {
            this.oribitItem = oribitItem;
        }

        public T getOribitItem() {
            return oribitItem;
        }
    }

    public interface OnItemClickListener {

        public void onItemClick(View contentView, OribitItem item);
    }
}

用法:

 OribitView oribitView = findViewById(R.id.oribitView);

        oribitView.setOnItemClickListener(new OribitView.OnItemClickListener() {
            @Override
            public void onItemClick(View contentView, OribitView.OribitItem item) {
                Toast.makeText(contentView.getContext(),item.text,Toast.LENGTH_SHORT).show();
            }
        });

        List<OribitView.OribitItem> oribitItems = new ArrayList<>();
        String[] chs = new String[]{"鮮花", "牛奶", "橘子", "生活", "新聞", "熱點"};
        int[] colors = new int[]{argb(random.nextFloat(), random.nextFloat(), random.nextFloat()),
                argb(random.nextFloat(), random.nextFloat(), random.nextFloat()),
                argb(random.nextFloat(), random.nextFloat(), random.nextFloat()),
                argb(random.nextFloat(), random.nextFloat(), random.nextFloat()),
                argb(random.nextFloat(), random.nextFloat(), random.nextFloat()),
                argb(random.nextFloat(), random.nextFloat(), random.nextFloat())
        };
        for (int i = 0; i < chs.length; i++) {
            OribitView.OribitItem item = new OribitView.OribitItem();
            item.text = chs[i];
            item.textColor = Color.WHITE;
            item.backgroundColor = colors[i];

            oribitItems.add(item);
        }

        oribitView.showItems(oribitItems);

三、總結(jié)

本篇難點主要是事件處理,當(dāng)然可能有人會問,使用Layout添加豈不是更方便,答案是肯定的,但是本篇主要重點介紹Canvas 繪制,后續(xù)有Layout的布局,當(dāng)然這里其實區(qū)別并不大,不同點是一個需要onLayout的調(diào)用,另一個是onDraw的調(diào)用,做好坐標軸轉(zhuǎn)換即可,難度并不大。

以上就是Android自定義實現(xiàn)轉(zhuǎn)盤菜單的詳細內(nèi)容,更多關(guān)于Android轉(zhuǎn)盤菜單的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • android截屏功能實現(xiàn)代碼

    android截屏功能實現(xiàn)代碼

    這篇文章主要為大家詳細介紹了android截屏功能的實現(xiàn)代碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • Android的webview支持HTML5的離線應(yīng)用功能詳細配置

    Android的webview支持HTML5的離線應(yīng)用功能詳細配置

    HTML5的離線應(yīng)用功能可以使得WebApp即使在網(wǎng)絡(luò)斷開的情況下仍能正常使用這是個非常有用的功能,但如何使Webivew支持HTML5離線應(yīng)用功能呢,需要的朋友可以參考下
    2012-12-12
  • Android直播app送禮物連擊動畫效果(實例代碼)

    Android直播app送禮物連擊動畫效果(實例代碼)

    最近在做公司的直播項目,需要實現(xiàn)一個觀看端連擊送禮物的控件,下面給大家分享實例代碼,需要的的朋友參考下吧
    2017-07-07
  • Android開發(fā)Retrofit源碼分析

    Android開發(fā)Retrofit源碼分析

    這篇文章主要為大家介紹了Android開發(fā)Retrofit源碼分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • Android App在線程中創(chuàng)建handler的方法講解

    Android App在線程中創(chuàng)建handler的方法講解

    這篇文章主要介紹了Android App在線程中創(chuàng)建handler的方法講解,文中同時講解了handler和線程的關(guān)系以及使用Handler時一些需要注意的地方,需要的朋友可以參考下
    2016-03-03
  • Android自定義星星可滑動評分控件

    Android自定義星星可滑動評分控件

    這篇文章主要介紹了Android自定義星星可滑動評分控件,通過線性布局結(jié)合ImageView實現(xiàn)評分控件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Android自定義彈框樣式

    Android自定義彈框樣式

    這篇文章主要為大家詳細介紹了Android自定義彈框樣式,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-08-08
  • Android?控件自動貼邊實現(xiàn)實例詳解

    Android?控件自動貼邊實現(xiàn)實例詳解

    這篇文章主要為大家介紹了Android?控件自動貼邊實現(xiàn)實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Android截屏分享功能

    Android截屏分享功能

    最近項目經(jīng)理交給我一個任務(wù),要求實現(xiàn)android截屏功能,包括Android截屏獲取圖片、將圖片保存到本地、通知系統(tǒng)相冊更新、通過微信、QQ、微博分享截屏圖片。小編把實現(xiàn)思路分享到腳本之家平臺,需要的朋友參考下
    2017-12-12
  • Android Studio實現(xiàn)搜索欄

    Android Studio實現(xiàn)搜索欄

    這篇文章主要為大家詳細介紹了Android Studio實現(xiàn)搜索欄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-04-04

最新評論

左权县| 肇东市| 东乡县| 呼和浩特市| 临清市| 潜江市| 美姑县| 平阳县| 宿州市| 永丰县| 湖口县| 龙游县| 信阳市| 吉隆县| 克什克腾旗| 佛山市| 太仆寺旗| 锦州市| 霍林郭勒市| 定日县| 龙南县| 临澧县| 乾安县| 安塞县| 洞口县| 南岸区| 津南区| 南乐县| 泾阳县| 安平县| 名山县| 汤原县| 连云港市| 宜黄县| 安乡县| 鄄城县| 湾仔区| 神农架林区| 化州市| 武安市| 通州市|