GossipView:圓圈布局的自定義view

chenyigis 8年前發布 | 7K 次閱讀 Android開發 移動開發

 當我們想展示一個數組形式的數據的時候,要么是使用列表的形式,要么是使用網格的形式,今天我們介紹一種奇葩的形式,圓圈形式:

注意,周邊的扇形是可以點擊的。如果使用現有控件,要實現起來是有難度的,所以我們就采用了自定義View的方式。

下面是原理以及使用方法,整個項目可以到這里下載 http://jcodecraeer.com/a/opensource/2014/1115/1988.html


繪制

主要是外部扇形以及內部圓圈背景的繪制,最里面其實還有個很細的圓圈,那個其實是用在當想顯示加載效果的時候,但這不是重點略去不講。

內部圓圈背景的繪制很簡單直接使用Drawable 的draw方法,前提是先設置好bounds(下面會講到).

mInnerBackGroud.draw(canvas);

而外部扇形的繪制是分別調用drawArc方法(也許應該取別的名字,和canvas的方法沖突了)完成的:

 

for(int i = 0;i < mPieceNumber ; i++){
    drawArc(i , canvas);
}

有多少個扇形調用多少次。

drawArc定義如下:

 

/** 按索引值繪制扇區,第一個扇區的中心位于3點鐘方向*/
public void drawArc(int index , Canvas canvas){
    int startdegree  =  mPieceDegree * (index) - (mPieceDegree - mDividerDegree) / 2;
    if(index == mSelectIndex){
        mOuterArcPaint.setColor(0xFFcacccc);
    }else{
        mOuterArcPaint.setColor(outArcColor[index]);
    }
    float radious  = ((float)mWidth - (float)outArctrokeWidth) / 2 - padding ;
    float midDegree = startdegree + ( mPieceDegree  - mDividerDegree) /2 ;
    double x  = radious * Math.cos(midDegree * Math.PI/180);
    double y  = radious * Math.sin(midDegree  * Math.PI/180);
    x = x + getOriginal().x;
    y = y + getOriginal().y;
    canvas.drawArc(mOuterArcRectangle, startdegree, mPieceDegree  - mDividerDegree, false, mOuterArcPaint);
    Rect rect = new Rect();
    mOuterTextPaint.getTextBounds(items.get(index).getTitle(), 0, items.get(index).getTitle().length(), rect);
    int txWidth  = rect.width();
    int txHeight = rect.height();
    canvas.drawText(items.get(index).getTitle(), (int)x - txWidth/2, (int)y + txHeight/2, mOuterTextPaint);
}

空間計算:

 

根據onMeasure方法中的寬和高計算出不同區域的基本參數,比如外扇形的厚度outArctrokeWidth,外扇形文字的大小mOuterTextPaint,外扇形的半徑mOuterArcRadius,外扇形繪制的矩形區域mOuterArcRectangle;以及內部圓圈mInnerBackGroud的bounds。

 

按下效果:

外部扇形的按下效果是根據不同狀態下設置畫筆的顏色來實現的:

如果某一個扇形的索引剛好等于選中的mSelectIndex,則設置按下的顏色:

if(index == mSelectIndex){
    mOuterArcPaint.setColor(0xFFcacccc);
}else{
    mOuterArcPaint.setColor(outArcColor[index]);
}

而內部的圓圈部分則直接采用selector圖片的方式:

if(mSelectIndex == -1){
    Log.i(TAG,"mSelectIndex = "+mSelectIndex);
    mInnerBackGroud.setState(PRESSED_FOCUSED_STATE_SET);
}else{
    mInnerBackGroud.setState(EMPTY_STATE_SET);
}

mSelectIndex == -1 表示選中的是最中間的圓圈,若為真設置Drawable mInnerBackGroud的狀態為PRESSED_FOCUSED_STATE_SET,反之為EMPTY_STATE_SET,因為mInnerBackGroud其實是由selector得來的Drawable ,所以只要設置了不同的狀態就會繪出不同的圖片效果。

 

另外為了處理扇形和內部圓圈的按下效果,我們必須判斷當前到底是點中了那部分

@Override
public boolean onTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        mSelectIndex = getTouchArea(new Point(event.getX() , event.getY()));
        this.invalidate();
        Log.i(TAG ,"mSelectIndex =" +mSelectIndex);
        //mSelectIndex = -1;
    }else if(event.getAction() == MotionEvent.ACTION_UP && event.getAction() != MotionEvent.ACTION_CANCEL){
        int upIndex = getTouchArea(new Point(event.getX() , event.getY()));
        if(mListener != null){
            mListener.onPieceClick(upIndex);
        }
        mSelectIndex = -2;
        this.invalidate();
    }else if(event.getAction() == MotionEvent.ACTION_CANCEL){
        mSelectIndex = -2;
        this.invalidate();
    }
    return true;
}

在ACTION_DOWN事件中,調用getTouchArea來判斷當前選中的是什么,getTouchArea可能返回的有三種值:

-1 選中的是最中間的圓圈

-2 選中的是扇形之間的間隔部分

整數:選中的是某個扇形。

ACTION_UP事件發生之后我們通知UI重繪,并且調用onPieceClick通知注冊的Lisetner我選擇了什么。


監聽選中了什么

GossipView.OnPieceClickListener

public interface OnPieceClickListener {
    void onPieceClick(int whitchPiece);
}

這個不用解釋了吧,最常見的觀察者模式。

 

下面是使用方法:

xml中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jcodecraeer.gossipview.MainActivity" >
    <com.jcodecraeer.gossipview.GossipView
        android:id="@+id/gossipview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</RelativeLayout>

Activity中:

...
        GossipView gossipView = (GossipView)findViewById(R.id.gossipview);
        String [] strs = {"安卓","微軟","蘋果","谷歌","百度","騰訊"} ;

    final List<GossipItem> items =new ArrayList<GossipItem>();
    for(int i = 0; i < strs.length; i++) {
        GossipItem item = new GossipItem(strs[i],3);
        items.add(item);
    }
    gossipView.setItems(items);
    gossipView.setNumber(3);
    gossipView.setOnPieceClickListener( new GossipView.OnPieceClickListener(){
        @Override
        public void onPieceClick(int index) {
          if(index != -1 &&  index != -2) {
              Toast.makeText(MainActivity.this, "你選擇了" + items.get(index).getTitle(), 300).show();
          }
        }
    });

....</pre>

GossipItem的定義:

package com.jcodecraeer.gossipview;
public class GossipItem  {
    private String title;
    private int index;
    public GossipItem (String title,int index){
        this.title =title;
        this.index = index;
    }

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}
public int getIndex() {
    return index;
}

public void setIndex(int index) {
    this.index = index;
}

}</pre>

轉載請注明出處:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1115/1986.html

 

 本文由用戶 chenyigis 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!