GossipView:圆圈布局的自定义view

当我们想展示一个数组形式的数据的时候,要么是使用列表的形式,要么是使用网格的形式,今天我们介绍一种奇葩的形式,圆圈形式:

注意,周边的扇形是可以点击的。如果使用现有控件,要实现起来是有难度的,所以我们就采用了自定义View的方式。

下面是原理以及使用方法,整个项目可以到这里下载:https://github.com/jianghejie/GossipView

绘制

主要是外部扇形以及内部圆圈背景的绘制,最里面其实还有个很细的圆圈,那个其实是用在当想显示加载效果的时候,但这不是重点略去不讲。

内部圆圈背景的绘制很简单直接使用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();
              }
            }
        });
....

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;
    }
}

转载自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1115/1986.html

时间: 2024-10-12 21:20:36

GossipView:圆圈布局的自定义view的相关文章

【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree. 每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己. 因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制. 绘制是一个两遍(two pass)的过程:一个mea

自定义View(二)--继承自ViewGroup

自定义View包括很多种,上一次随笔中的那一种是完全继承自View,这次写的这个小Demo是继承自ViewGroup的,主要是将自定义View继承自ViewGroup的这个流程来梳理一下,这次的Demo中自定义了一个布局的效果,并且这个自定义布局中包含布局自己的属性,布局中的控件也包含只属于这个布局才具有的自定义属性(类似于layout_weight只存在于LinearLayout中,只有LinearLayout中的控件可以使用一样).话不多说,先看效果图: 其中红色的部分是自定义的ViewGr

Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree. 每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己. 因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制. 绘制是一个两遍(two pass)的过程:一个mea

实现自定义View的三种方式

一.组合控件 组合控件,顾名思义,就是将系统原有的控件进行组合,构成一个新的控件.这种方式下,不需要开发者自己去绘制图上显示的内容,也不需要开发者重写onMeasure,onLayout,onDraw方法来实现测量.布局以及draw流程.所以,在实现自定义view的三种方式中,这一种相对比较简单. 实际开发中,标题栏就是一个比较常见的例子.因为在一个app的各个界面中,标题栏基本上是大同小异,复用率很高.所以经常会将标题栏单独做成一个自定义view,在不同的界面直接引入即可,而不用每次都把标题栏

继承于Layout的自定义View减少布局层次

不管是为了封装也好,实现特殊的效果也好,大家或多或少都会进行自定义View的实践,这中间又主要有两种:一种是继承于View或ViewGroup,还有一个是继承于各种已存在的Layout使用XML来写. 今天要来讨论的是第二种,实践就不详细说了,这里主要是针对这种方式带来的布局层次过深的问题提出两个方案. 第一种,注意在布局xml中使用merge,千万不要误解这个只在FrameLayout时候才能用哦,这个的准确作用是在解析XML布局时由此标志位就不解析直接将这一层忽略,将下面层次的view直接添

自定义View的ToolBar布局报错Error:(2) No resource identifier found for attribute &#39;context&#39; in package &#39;c

这是由于你的自定义xmlns出错, 先上代码: 出错的布局文件 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" andro

九点(九宫格)式手势解锁自定义view

周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事.把源码和资源贴出来,给大家分享,希望对大家有用. 效果,就跟手机上的九点手势解锁一样,上个图吧: 过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了.都在代码里,所以这里就主要是贴资源吧. 这个自定义view就一个类,源码如下: package com.cc.library.view; import android.

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android知识梳理之自定义View

虽然android本身给我们提供了形形色色的控件,基本能够满足日常开发的需求,但是面对日益同质化的app界面,和不同的业务需求.我们可能就需要自定义一些View来获得比较好的效果.自定义View是android开发者走向高级开发工程师必须要走的一关. 转载请标明出处:http://blog.csdn.net/unreliable_narrator/article/details/51274264 一,构造函数: 当我们创建一个类去继承View的时候,会要求我们至少去实现一个构造函数. publi