Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android系統教程 >> 安卓省電與加速 >> 自定義View仿360懸浮球與加速球

自定義View仿360懸浮球與加速球

編輯:安卓省電與加速

先來看一張動態圖

這裡寫圖片描述vcbBxLvSu7Lgo6zH0s3Ptq/KsbvhseSzydK71cXNvMasPGJyIC8+DQoyLiC1sbXju/fQ/Lihx/LKsaOs0Py4ocfy0v6y2KOstdeyv7P2z9bSu7j2vNPL2cfyo6zLq7v3vNPL2cfyyrGjrLPKz9bLrsG/1vC9pdT2uN/H0rKotq+3+bbIvc/QobXE0Ke5+6OstaW798qxsqjAy8nPz8KyqLavx9K3+bbIvaXQoTxiciAvPg0KMy4gteO798bBxLuyu7D8uqy117K/vNPL2cfytcSyv867o6y808vZx/K74dL+stijrND8uKHH8tbY0MKz9s/WPC9wPg0KPHA+0qrX9rP21eLDtNK7uPbQp7n7o6zQ6NKqwb249tfUtqjS5VZpZXfT69K7uPbX1Lao0uVWaWV3R3JvdXA8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160812/20160812095047439.png" title="\" />

首先,需要先設計懸浮球View——FloatBall
簡單起見,為FloatBall指定一個默認寬度和高度——150像素
然後在onDraw(Canvas canvas)方法中,判斷FloatBall是否正在被拖動isDrag,如果是,則繪制一張默認圖片bitmap,否則則根據繪圖函數繪制圓形與居中文本

/**
 * Created by ZY on 2016/8/10.
 * 懸浮球
 */
public class FloatBall extends View {

    public int width = 150;

    public int height = 150;
    //默認顯示的文本
    private String text = "50%";
    //是否在拖動
    private boolean isDrag;

    private Paint ballPaint;

    private Paint textPaint;

    private Bitmap bitmap;

    public FloatBall(Context context) {
        super(context);
        init();
    }

    public FloatBall(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FloatBall(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init() {
        ballPaint = new Paint();
        ballPaint.setColor(Color.GRAY);
        ballPaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setTextSize(25);
        textPaint.setColor(Color.WHITE);
        textPaint.setAntiAlias(true);
        textPaint.setFakeBoldText(true);

        Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.ninja);
        //將圖片裁剪到指定大小
        bitmap = Bitmap.createScaledBitmap(src, width, height, true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!isDrag) {
            canvas.drawCircle(width / 2, height / 2, width / 2, ballPaint);
            float textWidth = textPaint.measureText(text);
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            float dy = -(fontMetrics.descent + fontMetrics.ascent) / 2;
            canvas.drawText(text, width / 2 - textWidth / 2, height / 2 + dy, textPaint);
        } else {
            //正在被拖動時則顯示指定圖片
            canvas.drawBitmap(bitmap, 0, 0, null);
        }
    }

    //設置當前移動狀態
    public void setDragState(boolean isDrag) {
        this.isDrag = isDrag;
        invalidate();
    }
}

因為FloatBall是不存在於Activity中而在屏幕單獨顯示的,所以需要用WindowManager來添加View並顯示
新建一個類,命名為ViewManager,用來總的管理View的顯示與刪除
私有化構造函數並采用單例模式

    private static ViewManager manager;

    //私有化構造函數
    private ViewManager(Context context) {
        this.context = context;
        init();
    }

    //獲取ViewManager實例
    public static ViewManager getInstance(Context context) {
        if (manager == null) {
            manager = new ViewManager(context);
        }
        return manager;
    }

ViewManager包含有顯示與隱藏懸浮球與加速球的函數

//顯示浮動小球
    public void showFloatBall() {
        if (floatBallParams == null) {
            floatBallParams = new LayoutParams();
            floatBallParams.width = floatBall.width;
            floatBallParams.height = floatBall.height - getStatusHeight();
            floatBallParams.gravity = Gravity.TOP | Gravity.LEFT;
            floatBallParams.type = LayoutParams.TYPE_TOAST;
            floatBallParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL;
            floatBallParams.format = PixelFormat.RGBA_8888;
        }
        windowManager.addView(floatBall, floatBallParams);
    }

    //顯示底部菜單
    private void showFloatMenu() {
        if (floatMenuParams == null) {
            floatMenuParams = new LayoutParams();
            floatMenuParams.width = getScreenWidth();
            floatMenuParams.height = getScreenHeight() - getStatusHeight();
            floatMenuParams.gravity = Gravity.BOTTOM;
            floatMenuParams.type = LayoutParams.TYPE_TOAST;
            floatMenuParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL;
            floatMenuParams.format = PixelFormat.RGBA_8888;
        }
        windowManager.addView(floatMenu, floatMenuParams);
    }

    //隱藏底部菜單
    public void hideFloatMenu() {
        if (floatMenu != null) {
            windowManager.removeView(floatMenu);
        }
    }

將懸浮球置於Service中開啟,這樣懸浮球就不那麼容易被系統去除了
在onCreate()方法中調用showFloatBall()

public class StartFloatBallService extends Service {

    public StartFloatBallService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        ViewManager manager = ViewManager.getInstance(this);
        manager.showFloatBall();
        super.onCreate();
    }
}

此時,只要為MainActivity添加一個按鈕,並設定當點擊按鈕後開啟Service,此時即可看到屏幕顯示了一個懸浮球

    public void startService(View view) {
        Intent intent = new Intent(this, StartFloatBallService.class);
        startService(intent);
        finish();
    }

不過此時懸浮球還不支持拖動與點擊,還需要為其添加OnTouchListener與OnClickListener

View.OnTouchListener touchListener = new View.OnTouchListener() {
            float startX;
            float startY;
            float tempX;
            float tempY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        startX = event.getRawX();
                        startY = event.getRawY();

                        tempX = event.getRawX();
                        tempY = event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float x = event.getRawX() - startX;
                        float y = event.getRawY() - startY;
                        //計算偏移量,刷新視圖
                        floatBallParams.x += x;
                        floatBallParams.y += y;
                        floatBall.setDragState(true);
                        windowManager.updateViewLayout(floatBall, floatBallParams);
                        startX = event.getRawX();
                        startY = event.getRawY();
                        break;
                    case MotionEvent.ACTION_UP:
                        //判斷松手時View的橫坐標是靠近屏幕哪一側,將View移動到依靠屏幕
                        float endX = event.getRawX();
                        float endY = event.getRawY();
                        if (endX < getScreenWidth() / 2) {
                            endX = 0;
                        } else {
                            endX = getScreenWidth() - floatBall.width;
                        }
                        floatBallParams.x = (int) endX;
                        floatBall.setDragState(false);
                        windowManager.updateViewLayout(floatBall, floatBallParams);
                        //如果初始落點與松手落點的坐標差值超過6個像素,則攔截該點擊事件
                        //否則繼續傳遞,將事件交給OnClickListener函數處理
                        if (Math.abs(endX - tempX) > 6 && Math.abs(endY - tempY) > 6) {
                            return true;
                        }
                        break;
                }
                return false;
            }
        };
        OnClickListener clickListener = new OnClickListener() {

            @Override
            public void onClick(View v) {
                windowManager.removeView(floatBall);
                showFloatMenu();
                floatMenu.startAnimation();
            }
        };
        floatBall.setOnTouchListener(touchListener);
        floatBall.setOnClickListener(clickListener);

加速球ProgressBall的設計較為復雜,需要用到貝塞爾曲線來呈現波浪效果,且單擊雙擊的效果也需要分開呈現
同樣是讓ProgressBall繼承於View
進度值的意義在於限制水面最終上升到的高度,即根據目標進度值與最大進度值的比例來決定水面高度
波浪總的起伏次數Count用於在單擊加速球時,水面上下波動的次數

    //view的寬度
    private int width = 200;
    //view的高度
    private int height = 200;
    //最大進度值
    private final int maxProgress = 100;
    //當前進度值
    private int currentProgress = 0;
    //目標進度值
    private final int targetProgress = 70;
    //是否為單擊
    private boolean isSingleTop;
    //設定波浪總的起伏次數
    private final int Count = 20;
    //當前起伏次數
    private int currentCount;
    //初始振幅大小
    private final int startAmplitude = 15;
    //波浪周期性出現的次數
    private final int cycleCount = width / (startAmplitude * 4) + 1;

初始化畫筆與監聽函數

private void init() {
        //初始化小球畫筆
        ballPaint = new Paint();
        ballPaint.setAntiAlias(true);
        ballPaint.setColor(Color.argb(0xff, 0x3a, 0x8c, 0x6c));
        //初始化(波浪)進度條畫筆
        progressPaint = new Paint();
        progressPaint.setAntiAlias(true);
        progressPaint.setColor(Color.argb(0xff, 0x4e, 0xc9, 0x63));
        progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //初始化文字畫筆
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(25);

        handler = new Handler();
        path = new Path();
        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);

        //手勢監聽
        //重點在於將單擊和雙擊操作分隔開
        SimpleOnGestureListener listener = new SimpleOnGestureListener() {
            //雙擊
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                //當前波浪起伏次數為零,說明“單擊效果”沒有影響到現在
                if (currentCount == 0) {
                    //當前進度為零或者已達到目標進度值,說明“雙擊效果”沒有影響到現在,此時可以允許進行雙擊操作
                    if (currentProgress == 0 || currentProgress == targetProgress) {
                        currentProgress = 0;
                        isSingleTop = false;
                        startDoubleTapAnimation();
                    }
                }
                return super.onDoubleTap(e);
            }

            //單擊
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                //當前進度值等於目標進度值,且當前波動次數為零,則允許進行單擊操作
                if (currentProgress == targetProgress && currentCount == 0) {
                    isSingleTop = true;
                    startSingleTapAnimation();
                }
                return super.onSingleTapConfirmed(e);
            }
        };
        gestureDetector = new GestureDetector(context, listener);
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return gestureDetector.onTouchEvent(event);
            }
        });
        //接受點擊操作
        setClickable(true);
    }

單擊或雙擊後的漸變效果是利用Handler的postDelayed(Runnable r, long delayMillis)方法來實現的,可以設定一個延時時間去執行Runnable ,然後在Runnable 中再次調用自身

class DoubleTapRunnable implements Runnable {
        @Override
        public void run() {
            if (currentProgress < targetProgress) {
                invalidate();
                handler.postDelayed(doubleTapRunnable, 50);
                currentProgress++;
            } else {
                handler.removeCallbacks(doubleTapRunnable);
            }
        }
    }

    //開啟雙擊動作動畫
    public void startDoubleTapAnimation() {
        handler.postDelayed(doubleTapRunnable, 50);
    }

    class SingleTapRunnable implements Runnable {
        @Override
        public void run() {
            if (currentCount < Count) {
                invalidate();
                currentCount++;
                handler.postDelayed(singleTapRunnable, 100);
            } else {
                handler.removeCallbacks(singleTapRunnable);
                currentCount = 0;
            }
        }
    }

    //開啟單擊動作動畫
    public void startSingleTapAnimation() {
        handler.postDelayed(singleTapRunnable, 100);
    }

onDraw(Canvas canvas)的重點在於根據比例值來計算水面高度

@Override
    protected void onDraw(Canvas canvas) {
        //繪制圓形
        bitmapCanvas.drawCircle(width / 2, height / 2, width / 2, ballPaint);
        path.reset();
        //高度隨當前進度值的變化而變化
        float y = (1 - (float) currentProgress / maxProgress) * height;
        //屬性PorterDuff.Mode.SRC_IN代表了progressPaint只顯示與下層層疊的部分,
        //所以以下四點雖然連起來是個矩形,可呈現出來的依然是圓形
        //右上角
        path.moveTo(width, y);
        //右下角
        path.lineTo(width, height);
        //左下角
        path.lineTo(0, height);
        //左上角
        path.lineTo(0, y);
        //繪制頂部波浪
        if (!isSingleTop) {
            //是雙擊
            //根據當前進度大小調整振幅大小,有逐漸減小的趨勢
            float tempAmplitude = (1 - (float) currentProgress / targetProgress) * startAmplitude;
            for (int i = 0; i < cycleCount; i++) {
                path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);
                path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);
            }
        } else {
            //是單擊
            //根據當前次數調整振幅大小,有逐漸減小的趨勢
            float tempAmplitude = (1 - (float) currentCount / Count) * startAmplitude;
            //因為想要形成波浪上下起伏的效果,所以根據currentCount的奇偶性來變化貝塞爾曲線轉折點位置
            if (currentCount % 2 == 0) {
                for (int i = 0; i < cycleCount; i++) {
                    path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);
                    path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);
                }
            } else {
                for (int i = 0; i < cycleCount; i++) {
                    path.rQuadTo(startAmplitude, -tempAmplitude, 2 * startAmplitude, 0);
                    path.rQuadTo(startAmplitude, tempAmplitude, 2 * startAmplitude, 0);
                }
            }
        }
        path.close();
        bitmapCanvas.drawPath(path, progressPaint);
        String text = (int) (((float) currentProgress / maxProgress) * 100) + "%";
        float textWidth = textPaint.measureText(text);
        Paint.FontMetrics metrics = textPaint.getFontMetrics();
        float baseLine = height / 2 - (metrics.ascent + metrics.descent);
        bitmapCanvas.drawText(text, width / 2 - textWidth / 2, baseLine, textPaint);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

因為要呈現ProgressBall時不僅僅是其本身,或者還需要背景色或者文本之類的內容,所以可以將其置於ViewGroup中來顯示
布局文件




    

        

        

    

FloatMenu就作為容納ProgressBall的容器,並為其賦予從下往上滑動顯示的動畫效果

/**
 * Created by ZY on 2016/8/10.
 * 底部菜單欄
 */
public class FloatMenu extends LinearLayout {

    private LinearLayout layout;

    private TranslateAnimation animation;

    public FloatMenu(final Context context) {
        super(context);
        View root = View.inflate(context, R.layout.float_menu, null);
        layout = (LinearLayout) root.findViewById(R.id.layout);
        animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, 1.0f,
                Animation.RELATIVE_TO_SELF, 0);
        animation.setDuration(500);
        animation.setFillAfter(true);
        layout.setAnimation(animation);
        root.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                ViewManager manager = ViewManager.getInstance(context);
                manager.showFloatBall();
                manager.hideFloatMenu();
                return false;
            }
        });
        addView(root);
    }

    public void startAnimation() {
        animation.start();
    }
}

這裡提供源代碼下載:http://yunpan.cn/c6Un9qvIYKpkx 訪問密碼 199e

  1. 上一頁:
  2. No
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved