1. 程式人生 > >Android自定義控制元件(二)-給自定義控制元件新增事件

Android自定義控制元件(二)-給自定義控制元件新增事件

在這篇部落格中主要講解給Android自定義控制元件新增點選事件,實現可以按住百分比圓圈在螢幕上進行拖動圓圈的功能。分兩部分講,第一部分是獲取自定義控制元件的座標,第二部分是重新繪製控制元件。

第一部分:獲取自定義控制元件座標

首先看一張圖,這是自定義控制元件中獲取座標的函式,各函式獲取的座標含義如下所示:

1.view獲取自身座標:getLeft(),getTop(),getRight(),getBottom()

getTop:獲取到的,是view自身的頂邊到其父佈局頂邊的距離

getLeft:獲取到的,是view自身的左邊到其父佈局左邊的距離

getRight:獲取到的,是view自身的右邊到其父佈局左邊的距離

getBottom:獲取到的,是view自身的底邊到其父佈局頂邊的距離

2.view獲取自身寬高:getHeight(),getWidth()

3.motionEvent獲取座標:getX(),getY(),getRawX(),getRawY()

getX():獲取點選事件相對控制元件左邊的x軸座標,即點選事件距離控制元件左邊的距離

getY():獲取點選事件相對控制元件頂邊的y軸座標,即點選事件距離控制元件頂邊的距離

getRawX():獲取點選事件相對整個螢幕左邊的x軸座標,即點選事件距離整個螢幕左邊的距離

getRawY():獲取點選事件相對整個螢幕頂邊的y軸座標,即點選事件距離整個螢幕頂邊的距離

第二部分:計算控制元件左上角座標,當按下控制元件移動的時候,計算偏移距離,重新繪製,程式碼及效果圖如下所示:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SimpleView extends View {

    private final static String TAG = SimpleView.class.getSimpleName();
    //畫筆
    private Paint mPaint;
    private RectF oval;
    //事件處理
    private EventHandle mEventHandle;
    //滑鼠按下位置
    private int startX,startY;
    //按下滑鼠時控制元件的位置
    private int startLeft,startTop;
    //狀態列高度
    int statusHeight = 0;
    public SimpleView(Context context) {
        super(context);
        init();
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mPaint = new Paint();
        //設定是否使用抗鋸齒功能,會消耗較大資源,繪製圖形速度會變慢。
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(30.0f);
        oval=new RectF();
        mEventHandle=null;
        startY=startX=0;

        int resourceId = this.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusHeight = this.getResources().getDimensionPixelSize(resourceId);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                //精確值模式,當控制元件的layout_width和layout_height屬性指定為具體數值或match_parent時。
                break;
            case MeasureSpec.AT_MOST:
                //最大值模式,當空間的寬高設定為wrap_content時。
                break;
            case MeasureSpec.UNSPECIFIED:
                //未指定模式,View想多大就多大,通常在繪製自定義View時才會用。
                break;
        }
        //取最小邊為控制元件的寬高的最小值
        int minWidth=widthSize>heightSize?heightSize:widthSize;
        setMeasuredDimension(minWidth,minWidth);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.GRAY);
        // FILL填充, STROKE描邊,FILL_AND_STROKE填充和描邊
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        int with = getWidth();
        int height = getHeight();
        Log.e(TAG, "onDraw---->" + with + "*" + height);
        float radius = with / 2-5;
        canvas.drawCircle(with / 2, with / 2, radius, mPaint);
        mPaint.setColor(Color.RED);
        oval.set(with / 2 - radius, with / 2 - radius, with / 2
                + radius, with / 2 + radius);//用於定義的圓弧的形狀和大小的界限
        int sweepAngle=120;
        canvas.drawArc(oval, 0, -sweepAngle, true, mPaint);  //根據進度畫圓弧
        double percent=sweepAngle/360.0;
        //設定文字顏色
        mPaint.setColor(Color.WHITE);
        //繪製文字百分比資料
        canvas.drawText(String.format("%.2f",percent)+"%",(float)(with/2+radius*Math.cos(sweepAngle*Math.PI/360)/4)
                ,(float)(with/2-radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
        canvas.drawText(String.format("%.2f",1-percent)+"%",(float)(with/2-radius*Math.cos(sweepAngle*Math.PI/360))
                ,(float)(with/2+radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {


        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:

                startX=(int)event.getRawX();
                startY=(int)event.getRawY();
                startLeft=(int)(startX-event.getX());
                /**
                 * 這裡startTop計算有些偏離,原因在於計算時加入了標題欄和狀態列的高度
                 * 注意:要是你的Activity沒有去掉標題欄,這裡還要去掉標題欄的高度
                 */
                startTop= (int)(startY-event.getY())-statusHeight;//減去狀態列高度
                break;
            case MotionEvent.ACTION_MOVE:
                if(mEventHandle!=null)
                {
                    mEventHandle.onTouchEvent(event);
                }else{
                    int disX=(int)event.getRawX()-startX;//計算偏移的X座標
                    int disY=(int)event.getRawY()-startY;//計算偏移的Y座標;
                    int left=startLeft+disX;
                    int top=startTop+disY;
                    //更新控制元件位置
                    layout(left,top,left+getWidth(),top+getHeight());
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        //返回true表示不消耗此事件,事件繼續傳遞,返回flase表示事件消耗
        return true;
    }

    public void setmEventHandle(EventHandle mEventHandle) {
        this.mEventHandle = mEventHandle;
    }

    interface EventHandle{
        public void onTouchEvent(MotionEvent event);
    }
}

佈局檔案xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.myapplication.MainActivity">
    <com.example.myapplication.SimpleView
        android:layout_width="150dp"
        android:layout_height="150dp"
        />

</RelativeLayout>

MainActivity,要注意的是,需要在這裡將標題欄去掉,否則拖動的時候會出現偏差

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;


public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉標題欄
        /*
            requestWindowFeature(Window.FEATURE_NO_TITLE)無效解決方法:
            正常情況下requestWindowFeature(Window.FEATURE_NO_TITLE)是可以生效的,
            但是當Activity繼承子AppCompatActivity的時候,這個就失效了 
            解決辦法:
            1、手動在oncreate裡呼叫hide()
            if (getSupportActionBar() != null){
               getSupportActionBar().hide();
            }
         */
        if (getSupportActionBar() != null){
            getSupportActionBar().hide();
        }
        setContentView(R.layout.activity_main);
    }

}

效果如下圖: