1. 程式人生 > >繪製一個漂亮的弧形 View

繪製一個漂亮的弧形 View

繪製一個漂亮的弧形 View

來,先看下效果圖:

怎麼樣?漂亮吧,上面是一個標題欄,下面是一個弧形的 View,標題欄和弧形 View 從左到右都有一個線性的漸變色,類似於手機 QQ 頂部的漸變效果,關於弧形的這種效果,使用的場景還是比較普遍的,小面我們就一起來看看如何繪製吧。

1.先自定義一個 ArcView

建立一個類 ArcView 繼承於 View,然後我們重寫其三個構造方法,還有 onMeasure() 和 onDraw() 方法,這都是自定義 View 最基本的寫法,比較簡單這裡就不詳細說了, onMeasure() 用於測量 View 的寬高尺寸,onDraw() 就是具體的繪製過程了,具體的繪製思路是:我們在 onMeasure()方法中拿到測量出來的寬和高,然後再 onDraw() 中初始化一個畫筆,我們可以建立一個 LinearGradient 物件,然後呼叫設定畫筆的 setShader() 方法就可以達到畫筆漸變色的效果,然後再建立一個 Rect 物件,這裡只是建立了一個矩形物件,要想繪製弧形,我們還需要設定繪製的路徑,建立一個 Path 物件,分別呼叫 moveTo() 方法,和 quadTo() 方法,moveTo ()不會進行繪製,只用於移動移動畫筆,一般和其他方法配合使用,這裡和 quadTo() 方法配合使用,而 quadTo() 用於繪製圓滑的曲線,即貝塞爾曲線。mPath.quadTo(x1, y1, x2, y2) 其中x1、y1 為控制點,x2、y2 為結束點。有了路徑之後我們就可以輕而易舉地繪製出一個弧形了,完整的程式碼請看下面:

/**
 * Created by x-sir on 2018/8/10 :)
 * Function:自定義弧形 View
 */
public class ArcView extends View {

    private int mWidth;
    private int mHeight;
    private int mArcHeight; // 弧形高度
    private int mBgColor; // 背景顏色
    private Paint mPaint;

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

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

    public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcView);
        mArcHeight = typedArray.getDimensionPixelSize(R.styleable.ArcView_arcHeight, 0);
        mBgColor = typedArray.getColor(R.styleable.ArcView_bgColor, Color.parseColor("#1E90FF"));
        typedArray.recycle();
        mPaint = new Paint();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 引數一為漸變起初點座標 x 位置,引數二為 y 軸位置,引數三和四分辨對應漸變終點,最後引數為平鋪方式,這裡設定為映象
        LinearGradient lg = new LinearGradient(0, 0, mWidth, 0,
                Color.parseColor("#4796FB"), Color.parseColor("#5AB4F9"),
                Shader.TileMode.CLAMP);
        // 剛才已經講到 Gradient 是基於 Shader 類,所以我們通過 Paint 的 setShader 方法來設定這個漸變
        mPaint.setShader(lg);
        mPaint.setStyle(Paint.Style.FILL);
        //mPaint.setColor(mBgColor);
        mPaint.setAntiAlias(true);
        Rect rect = new Rect(0, 0, mWidth, mHeight - mArcHeight);
        canvas.drawRect(rect, mPaint);
        Path path = new Path();
        path.moveTo(0, mHeight - mArcHeight);
        path.quadTo(mWidth / 2, mHeight, mWidth, mHeight - mArcHeight);
        canvas.drawPath(path, mPaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth = widthSize;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        }
        setMeasuredDimension(mWidth, mHeight);
    }
}

我這裡只定義了弧形的高度和背景顏色這兩個自定義屬性,其中還有漸變顏色的開始色值和結束色值,還有是否需要漸變等屬性,都可以定義為自定義的屬性,方便直接在佈局檔案中靈活進行配置,以適應不同的需求,也比較簡單,我這裡為了就不實現了。

2.自定義一個通用的 titlebar

這樣做我們可以提高程式碼的複用性,可以將一些功能封裝進去,也更好地體現了封裝的思想,實現也比較簡單,使用 LayoutInflater 載入了一個佈局檔案,然後將 文字,字型大小,字型顏色和 title 背景色等定義成了自定義的屬性,方便在佈局檔案中進行設定,另外我們內部預設處理了點選返回按鈕的事件,對於需要其他特殊情況下返回按鈕的處理,我們也定義了一個介面,方便外部使用介面來自行進行處理,程式碼也非常簡單,看下面:

/**
 * Created by x-sir on 2018/9/2 :)
 * Function:自定義通用 TitleBar
 */
public class TitleBarLayout extends LinearLayout {

    private ImageView ivBack;
    private ImageView ivMenu;
    private TextView tvTitleName;
    private LinearLayout llTitleBg;
    private CharSequence mText;
    private int mTitleBgColor;
    private boolean mMenuVisible;
    private int mTextSize;
    private int mTextColor;
    private OnMenuClickListener mListener;
    private OnBackClickListener mOnBackListener;
    private static final String DEFAULT_TEXT = "Title"; // default text.
    private static final int DEFAULT_TEXT_SIZE = 16; // default text size.
    private static final String TAG = "TitleBarLayout";

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

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

    public TitleBarLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TitleBarLayout);
        mText = typedArray.getText(R.styleable.TitleBarLayout_text);
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.TitleBarLayout_textSize, DEFAULT_TEXT_SIZE);
        mTextColor = typedArray.getColor(R.styleable.TitleBarLayout_textColor, Color.parseColor("#FFFFFF"));
        mTitleBgColor = typedArray.getColor(R.styleable.TitleBarLayout_titleBgColor, Color.parseColor("#1E90FF"));
        mMenuVisible = typedArray.getBoolean(R.styleable.TitleBarLayout_menuVisible, false);
        typedArray.recycle();

        initView(context);
        initData();
        initListener();
    }

    private void initView(Context context) {
        LayoutInflater.from(context).inflate(R.layout.common_titlebar, this);
        ivBack = findViewById(R.id.ivBack);
        ivMenu = findViewById(R.id.ivMenu);
        tvTitleName = findViewById(R.id.tvTitleName);
        llTitleBg = findViewById(R.id.llTitleBg);
    }

    private void initData() {
        String text = (mText != null) ? mText.toString() : DEFAULT_TEXT;
        tvTitleName.setText(text);
        tvTitleName.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        tvTitleName.setTextColor(mTextColor);
        //llTitleBg.setBackgroundColor(mTitleBgColor);
        ivMenu.setVisibility(mMenuVisible ? VISIBLE : INVISIBLE);
    }

    private void initListener() {
        ivBack.setOnClickListener(v -> {
            // 如果監聽器不為空就讓其自己處理,為空就預設處理(銷燬頁面)
            if (mOnBackListener != null) {
                mOnBackListener.onClick();
            } else {
                ((Activity) getContext()).finish();
            }
        });
        ivMenu.setOnClickListener(v -> {
            if (mListener != null) {
                mListener.onClick();
            }
        });
    }

    public void setTitleName(String title) {
        if (!TextUtils.isEmpty(title)) {
            tvTitleName.setText(title);
        } else {
            Log.e(TAG, "set title name failed, because title is null!");
        }
    }

    public void setOnMenuListener(OnMenuClickListener mListener) {
        this.mListener = mListener;
    }

    public void setOnBackListener(OnBackClickListener mOnBackListener) {
        this.mOnBackListener = mOnBackListener;
    }

    public interface OnMenuClickListener {
        void onClick();
    }

    public interface OnBackClickListener {
        void onClick();
    }
}

因為這裡為了和繪製的弧形協調,我將 title 佈局的背景設定了一個漸變的 shape 。

3.將佈局延伸到系統狀態列

Android 5.0 之後我們可以設定 Activity 的佈局延伸到系統狀態列中,這樣我們頁面看起來更美觀和協調,設定也比較簡單,直接貼程式碼了:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
    }

另外在佈局檔案中也可以設定,只需在你 App 引用的主題中新增一行程式碼即可:

注意:因為這個屬性是 5.0 之後才有的,在實際開發中需要做相容處理,程式碼中設定時需要判斷一下版本,佈局檔案中可以新建一個 values-v21 資料夾做相容處理。

如果你的 title 的文字跑到狀態列裡了,那樣會很醜,在 title 佈局的根標籤中新增下面一行程式碼就可以了:

android:fitsSystemWindows="true"

這行程式碼的意思就是不佔用系統狀態列的空間。

好了,所有繪製工作都已經完成了,是不是非常簡單呢?如果覺得不錯的話,歡迎點贊、評論和轉發,你們的支援是我堅持不懈的動力!