From 5eacb5dfec251df164a1efd539d5b7862f75f0de Mon Sep 17 00:00:00 2001
From: jmAndroid <jay@huangjie.name>
Date: Mon, 25 Feb 2019 13:53:16 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8A=9F=E8=83=BD,v1.0?=
 =?UTF-8?q?=E7=89=88=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/src/main/AndroidManifest.xml              |   3 +
 .../jie/com/funnel/BaseFunnelActivity.java    |  24 +++
 .../com/funnel/CustomHalfWidthActivity.java   |  42 +++++
 .../jie/com/funnel/CustomLabelActivity.java   |  45 +++++
 .../java/jie/com/funnel/DefaultActivity.java  |  16 ++
 .../main/java/jie/com/funnel/FunnelData.java  |  63 ++++++-
 .../java/jie/com/funnel/MainActivity.java     |  50 ++---
 ...activity_main.xml => activity_default.xml} |  14 +-
 ...tomLabel.java => CustomLabelCallback.java} |   2 +-
 .../java/jie/com/funnellib/FunnelView.java    | 171 +++++++++++-------
 .../jie/com/funnellib/HalfWidthCallback.java  |  16 ++
 .../java/jie/com/funnellib/IFunnelData.java   |  13 +-
 .../src/main/java/jie/com/funnellib/Util.java |  24 ++-
 13 files changed, 369 insertions(+), 114 deletions(-)
 create mode 100644 app/src/main/java/jie/com/funnel/BaseFunnelActivity.java
 create mode 100644 app/src/main/java/jie/com/funnel/CustomHalfWidthActivity.java
 create mode 100644 app/src/main/java/jie/com/funnel/CustomLabelActivity.java
 create mode 100644 app/src/main/java/jie/com/funnel/DefaultActivity.java
 rename app/src/main/res/layout/{activity_main.xml => activity_default.xml} (53%)
 rename funnellib/src/main/java/jie/com/funnellib/{CustomLabel.java => CustomLabelCallback.java} (93%)
 create mode 100644 funnellib/src/main/java/jie/com/funnellib/HalfWidthCallback.java

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index da3715e..1a6e670 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,9 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".DefaultActivity" />
+        <activity android:name=".CustomLabelActivity" />
+        <activity android:name=".CustomHalfWidthActivity" />
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/app/src/main/java/jie/com/funnel/BaseFunnelActivity.java b/app/src/main/java/jie/com/funnel/BaseFunnelActivity.java
new file mode 100644
index 0000000..f1ac440
--- /dev/null
+++ b/app/src/main/java/jie/com/funnel/BaseFunnelActivity.java
@@ -0,0 +1,24 @@
+package jie.com.funnel;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import jie.com.funnellib.FunnelView;
+
+/**
+ * Created by hj on 2019/2/25.
+ * 说明:
+ */
+public abstract class BaseFunnelActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_default);
+        FunnelView funnelVie = findViewById(R.id.funnelView);
+        initEvent(funnelVie);
+    }
+
+    abstract void initEvent(FunnelView funnelView);
+}
diff --git a/app/src/main/java/jie/com/funnel/CustomHalfWidthActivity.java b/app/src/main/java/jie/com/funnel/CustomHalfWidthActivity.java
new file mode 100644
index 0000000..76eeeb0
--- /dev/null
+++ b/app/src/main/java/jie/com/funnel/CustomHalfWidthActivity.java
@@ -0,0 +1,42 @@
+package jie.com.funnel;
+
+import android.util.TypedValue;
+
+import jie.com.funnellib.FunnelView;
+import jie.com.funnellib.HalfWidthCallback;
+
+/**
+ * Created by hj on 2019/2/25.
+ * 说明:自定义宽度策略
+ */
+public class CustomHalfWidthActivity extends BaseFunnelActivity {
+
+    @Override
+    void initEvent(FunnelView funnelView) {
+        /**
+         * 自定义宽度策略,也就是漏斗每一层宽度增加多少,这都是可以自定义的,这样有利于适配的灵活性,也可以自定义出
+         * 很多的效果出来
+         * 注意事项:绘制是从下往上绘制的,halfWidth返回的当前的下面那个漏斗的宽度,需要注意一下
+         *
+         */
+        funnelView.setChartData(FunnelData.getTenCountData(), new HalfWidthCallback() {
+            @Override
+            public float getHalfStrategy(float halfWidth, int count, int i) {
+                /**
+                 * 这里定义的策略是前4个宽度不变,后面的逐渐增加10dp,所以呈现了一个真正的漏斗形状
+                 */
+                if (i <= 3) {
+                    halfWidth = dp2px(5);
+                    return halfWidth;
+                } else {
+                    halfWidth += dp2px(10);
+                    return halfWidth;
+                }
+            }
+        });
+    }
+
+    private float dp2px(int dip) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, this.getResources().getDisplayMetrics());
+    }
+}
diff --git a/app/src/main/java/jie/com/funnel/CustomLabelActivity.java b/app/src/main/java/jie/com/funnel/CustomLabelActivity.java
new file mode 100644
index 0000000..bea4e34
--- /dev/null
+++ b/app/src/main/java/jie/com/funnel/CustomLabelActivity.java
@@ -0,0 +1,45 @@
+package jie.com.funnel;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jie.com.funnellib.CustomLabelCallback;
+import jie.com.funnellib.FunnelView;
+import jie.com.funnellib.Util;
+
+/**
+ * Created by hj on 2019/2/25.
+ * 说明:此Activity演示自定义描述文字
+ */
+public class CustomLabelActivity extends BaseFunnelActivity {
+
+    @Override
+    void initEvent(FunnelView funnelView) {
+        final List<FunnelData> data = FunnelData.getFourCountData();
+
+        funnelView.addCustomLabelCallback(new CustomLabelCallback() {
+            @Override
+            public void drawText(Canvas canvas, Paint mPaintLabel, float labelX, float labelY, int index) {
+                FunnelData funnelData = data.get(index);
+                //先画前面的文字
+                mPaintLabel.setColor(funnelData.color);
+                mPaintLabel.setFakeBoldText(false);
+                canvas.drawText(funnelData.getLabel()+":", labelX, labelY, mPaintLabel);
+                //计算前面文字的长度
+                float labelWidth = Util.getTextWidth(mPaintLabel,funnelData.getLabel()+":");
+                mPaintLabel.setColor(Color.parseColor("#333333"));
+                mPaintLabel.setFakeBoldText(true);
+                //画后面的文字
+                canvas.drawText(funnelData.num+"个", labelX + labelWidth, labelY, mPaintLabel);
+            }
+        });
+        funnelView.setChartData(data);
+    }
+}
diff --git a/app/src/main/java/jie/com/funnel/DefaultActivity.java b/app/src/main/java/jie/com/funnel/DefaultActivity.java
new file mode 100644
index 0000000..0b74429
--- /dev/null
+++ b/app/src/main/java/jie/com/funnel/DefaultActivity.java
@@ -0,0 +1,16 @@
+package jie.com.funnel;
+
+
+import jie.com.funnellib.FunnelView;
+
+/**
+ * Created by hj on 2019/2/25.
+ * 说明:使用默认方式创建一个漏斗
+ */
+public class DefaultActivity extends BaseFunnelActivity {
+
+    @Override
+    void initEvent(FunnelView funnelView) {
+        funnelView.setChartData(FunnelData.getTenCountData());
+    }
+}
diff --git a/app/src/main/java/jie/com/funnel/FunnelData.java b/app/src/main/java/jie/com/funnel/FunnelData.java
index 4df3412..24dbe8d 100644
--- a/app/src/main/java/jie/com/funnel/FunnelData.java
+++ b/app/src/main/java/jie/com/funnel/FunnelData.java
@@ -1,5 +1,10 @@
 package jie.com.funnel;
 
+import android.graphics.Color;
+
+import java.util.ArrayList;
+import java.util.List;
+
 import jie.com.funnellib.IFunnelData;
 
 /**
@@ -7,14 +12,10 @@
  * 说明:
  */
 public class FunnelData implements IFunnelData {
-    public String value;
     public String label;
     public int color;
 
-    @Override
-    public String getValue() {
-        return value;
-    }
+    public String num;
 
     @Override
     public int getColor() {
@@ -25,4 +26,56 @@ public int getColor() {
     public String getLabel() {
         return label;
     }
+
+
+    //-----------------------------测试数据--------------------------------------------------------------
+    private static int[] colors = new int[]{
+            Color.parseColor("#FF5656"), Color.parseColor("#FF854B"),
+            Color.parseColor("#FFB240"), Color.parseColor("#FFEC3D"),
+            Color.parseColor("#4DD2F9"), Color.parseColor("#52B5FF"),
+            Color.parseColor("#5882FF"), Color.parseColor("#5959FF"),
+            Color.parseColor("#8359FF"), Color.parseColor("#AC59FF")
+    };
+
+    private static String[] labels = new String[]{
+            "Android", "ios",
+            "php", "c",
+            "c++", "python",
+            "golang", "java",
+            "javascript", ".net"
+    };
+
+
+    private static int[] fourColors = new int[]{
+            Color.parseColor("#FF5D5D"), Color.parseColor("#FFB240"),
+            Color.parseColor("#52B5FF"), Color.parseColor("#5882FF"),
+    };
+
+    private static String[] fourLabel = new String[]{
+            "数学", "语文", "物理", "化学"
+    };
+
+    public static List<FunnelData> getFourCountData() {
+        List<FunnelData> data = new ArrayList<>();
+        for (int i = 0; i < fourLabel.length; i++) {
+            FunnelData funnelData = new FunnelData();
+            funnelData.label = fourLabel[i];
+            funnelData.color = fourColors[i];
+            funnelData.num = String.valueOf(i);
+            data.add(funnelData);
+        }
+        return data;
+    }
+
+    public static List<FunnelData> getTenCountData() {
+        List<FunnelData> data = new ArrayList<>();
+        for (int i = labels.length - 1; i >= 0; i--) {
+            FunnelData funnelData = new FunnelData();
+            funnelData.label = labels[i];
+            funnelData.color = colors[i];
+            funnelData.num = String.valueOf(i);
+            data.add(funnelData);
+        }
+        return data;
+    }
 }
diff --git a/app/src/main/java/jie/com/funnel/MainActivity.java b/app/src/main/java/jie/com/funnel/MainActivity.java
index a579371..c157265 100644
--- a/app/src/main/java/jie/com/funnel/MainActivity.java
+++ b/app/src/main/java/jie/com/funnel/MainActivity.java
@@ -1,9 +1,15 @@
 package jie.com.funnel;
 
+import android.app.ListActivity;
+import android.content.Intent;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -15,34 +21,34 @@
  * Created by hj on 2019/2/22.
  * 说明:
  */
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends ListActivity {
 
-    private int[] colors = new int[]{
-            Color.parseColor("#FF5656"), Color.parseColor("#FF854B"),
-            Color.parseColor("#FFB240"), Color.parseColor("#FFEC3D"),
-            Color.parseColor("#4DD2F9"), Color.parseColor("#52B5FF"),
-            Color.parseColor("#5882FF"), Color.parseColor("#5959FF"),
-            Color.parseColor("#8359FF"), Color.parseColor("#AC59FF")
-    };
-
-    private int[] fourColors = new int[]{
-            Color.parseColor("#FF5D5D"), Color.parseColor("#FFB240"),
-            Color.parseColor("#52B5FF"), Color.parseColor("#5882FF"),
+    private final String[] data = new String[]{
+            "使用默认方式(Use default mode)",
+            "自定义描述文字(Custom Description Text)",
+            "使用自定义宽度策略(Use custom width policy)",
     };
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-        FunnelView funnelView = findViewById(R.id.funnel);
-        List<IFunnelData> data = new ArrayList<>();
-        for (int i = 0; i < 4; i++) {
-            FunnelData entity = new FunnelData();
-            entity.value = "A";
-            entity.color = fourColors[i];
-            entity.label = i+"";
-            data.add(entity);
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+                data);
+        setListAdapter(adapter);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        switch (position) {
+            case 0:
+                startActivity(new Intent(this, DefaultActivity.class));
+                break;
+            case 1:
+                startActivity(new Intent(this, CustomLabelActivity.class));
+                break;
+            case 2:
+                startActivity(new Intent(this,CustomHalfWidthActivity.class));
+                break;
         }
-        funnelView.setChartData(data);
     }
 }
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_default.xml
similarity index 53%
rename from app/src/main/res/layout/activity_main.xml
rename to app/src/main/res/layout/activity_default.xml
index 8a66acc..b84e6ae 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_default.xml
@@ -4,13 +4,11 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">
-  <jie.com.funnellib.FunnelView
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:id="@+id/funnel"
-      android:paddingLeft="10dp"
-      android:paddingTop="10dp"
-      android:background="#e4ebe8"
-      />
 
+    <jie.com.funnellib.FunnelView
+        android:id="@+id/funnelView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingTop="10dp" />
 </LinearLayout>
\ No newline at end of file
diff --git a/funnellib/src/main/java/jie/com/funnellib/CustomLabel.java b/funnellib/src/main/java/jie/com/funnellib/CustomLabelCallback.java
similarity index 93%
rename from funnellib/src/main/java/jie/com/funnellib/CustomLabel.java
rename to funnellib/src/main/java/jie/com/funnellib/CustomLabelCallback.java
index 58c726c..97f481e 100644
--- a/funnellib/src/main/java/jie/com/funnellib/CustomLabel.java
+++ b/funnellib/src/main/java/jie/com/funnellib/CustomLabelCallback.java
@@ -7,7 +7,7 @@
  * Created by hj on 2019/2/22.
  * 说明:开放自定义描述绘制画笔
  */
-public interface CustomLabel {
+public interface CustomLabelCallback {
     /**
      * 循环绘制线后面的文字
      * @param canvas 画布
diff --git a/funnellib/src/main/java/jie/com/funnellib/FunnelView.java b/funnellib/src/main/java/jie/com/funnellib/FunnelView.java
index c1e4856..627aff6 100644
--- a/funnellib/src/main/java/jie/com/funnellib/FunnelView.java
+++ b/funnellib/src/main/java/jie/com/funnellib/FunnelView.java
@@ -22,7 +22,9 @@
 
 /**
  * Created by hj on 2018/11/23.
- * 说明:漏斗View
+ * 说明:漏斗View,可自定义宽度,颜色,高度,描述文字,线宽,线颜色等...
+ * 详细操作请阅读README.md
+ * github: https://github.com/Jay-huangjie/FunnelView
  */
 public class FunnelView extends View {
     private static final String TAG = "FunnelChart";
@@ -55,11 +57,8 @@ public class FunnelView extends View {
     private float mLastLineOffset; //最底部从中心点向两边的偏移量
     private float mTotalHeight; //单个梯形的目标高度
     private int count; //漏斗的个数
-    private float mPlotRight;
-    private float mPlotLeft;
-    private float mPlotBottom;
-    private float mPlotTop;
-
+    private float mPlotBottom; //底部坐标
+    private boolean EXACTLY; //是否是精确高度
     /*
      * 中心点坐标,绘制是从下往上,这个坐标是最底部那跟线的中心点
      * 最后一根线的长度= mLastLineOffset*2
@@ -75,13 +74,18 @@ public class FunnelView extends View {
      * */
     private float mPlotWidth;
 
-    //最顶部的线的宽度
-    private float mTopMaxLineWidth;
+    //最长的线的宽度的一半
+    private float mTopMaxLineHalf;
 
     /*
      * 自定义绘制描述文字接口
      * */
-    private CustomLabel mCustomLabel;
+    private CustomLabelCallback mCustomLabelCallback;
+
+    /*
+     * 自定义漏斗宽度变化策略
+     * */
+    private HalfWidthCallback mHalfWidthCallback;
 
     /*
      * 漏斗之间线的颜色
@@ -107,6 +111,11 @@ public class FunnelView extends View {
      * */
     private float mLabelSize;
 
+    /*
+     * 宽度策略数据容器
+     * */
+    private float[] halfArrays;
+
     public FunnelView(Context context) {
         super(context);
         initView(context, null);
@@ -142,13 +151,6 @@ private void initView(Context context, AttributeSet attributeSet) {
     }
 
 
-    public void setChartData(@NonNull List<IFunnelData> chartData) {
-        this.mDataSet = chartData;
-        count = mDataSet.size();
-        mTopMaxLineWidth = mLastLineOffset + getHalfWidthOffset() * count;
-        invalidate();
-    }
-
     private void chartRender() {
         mPaintLabel = new Paint(Paint.ANTI_ALIAS_FLAG);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -174,9 +176,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
     protected void onDraw(Canvas canvas) {
         try {
             canvas.save();
-            //计算主图表区范围
             calcPlotRange();
-            //绘制图表
             renderPlot(canvas);
             canvas.restore();
         } catch (Exception e) {
@@ -189,7 +189,7 @@ private void renderPlot(Canvas canvas) {
             Log.e(TAG, "FunnelView=>未设置数据源!");
             return;
         }
-        float funnelHeight = mPlotHeight / count;
+        float funnelHeight = EXACTLY ? mPlotHeight / count : mTotalHeight;
         float cx = mCenterX;
         renderPlotDesc(canvas, cx, funnelHeight);
     }
@@ -214,28 +214,29 @@ private int measureWidth(int measureSpec) {
     }
 
     private int measureHeight(int measureSpec) {
-        int result = (int) mTotalHeight * count;
+        int result = (int) (mTotalHeight * count + mFunnelLineStoke * (count - 1));
         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec);
 
         if (specMode == MeasureSpec.EXACTLY) { //fill_parent
             result = specSize;
+            EXACTLY = true;
         }
         return result;
     }
 
 
     /**
-     * 计算图的显示范围,依屏幕px值来计算.
+     * 计算一些关键数据
      */
     private void calcPlotRange() {
         mPlotBottom = mBottom - getPaddingBottom();
-        mPlotTop = getPaddingTop();
-        mPlotLeft = getPaddingLeft();
-        mPlotRight = mRight - getPaddingRight();
+        float mPlotTop = getPaddingTop();
+        float mPlotLeft = getPaddingLeft();
+        float mPlotRight = mRight - getPaddingRight();
         mPlotWidth = Math.abs(mPlotRight - mPlotLeft);
         mPlotHeight = Math.abs(mPlotBottom - mPlotTop);
-        mCenterX = (int) mTopMaxLineWidth + mPlotLeft;
+        mCenterX = (int) mTopMaxLineHalf + mPlotLeft;
     }
 
     /**
@@ -259,14 +260,11 @@ private void renderPlotDesc(Canvas canvas, float cx, float funnelHeight) {
         int count = mDataSet.size();
         float halfWidth = 0.f; //梯形的半径
         float bottomY;
-
         PointF pStart = new PointF();
         PointF pStop = new PointF();
-
         pStart.x = cx - mPlotWidth / 2;
         pStop.x = cx + mPlotWidth / 2;
         pStart.y = pStop.y = mPlotBottom;
-
         float labelY = 0.f;
         Path path = new Path();
         for (int i = 0; i < count; i++) {
@@ -274,33 +272,23 @@ private void renderPlotDesc(Canvas canvas, float cx, float funnelHeight) {
             path.reset();
             if (i == 0) { //画底部的线,从左下角开始绘制
                 path.moveTo(cx - mLastLineOffset, mPlotBottom);
-                //底部线默认长度为100
+                //底部线默认长度为80
                 path.lineTo(cx + mLastLineOffset, mPlotBottom);
             } else {
                 path.moveTo(pStart.x, pStart.y);
                 path.lineTo(pStop.x, pStop.y);
             }
-            //根据数量来调整倾斜角度,如果需要实现别的倾斜效果只需调整下面的算法
-            if (count <= 4) {
-                halfWidth += dip2px(mContext, 17);
-            } else if (count <= 6) {
-                halfWidth += dip2px(mContext, 13);
-            } else if (count <= 8) {
-                halfWidth += dip2px(mContext, 10);
-            } else if (count <= 10) {
-                halfWidth += dip2px(mContext, 7);
+            //根据数量来调整倾斜角度,如果需要实现别的倾斜效果只需实现HalfWidthCallback接口
+            if (mHalfWidthCallback == null) {
+                halfWidth += getDefaultHalfWidthOffset();
             } else {
-                halfWidth += dip2px(mContext, 5);
+                halfWidth = halfArrays[i];
             }
             bottomY = sub(mPlotBottom, i * funnelHeight);
-
             labelY = bottomY - funnelHeight / 2;
-
             pStart.x = cx - mLastLineOffset - halfWidth;
             pStart.y = bottomY - funnelHeight;
-
             pStop.x = cx + mLastLineOffset + halfWidth;
-            Log.i("HJ", pStop.x + "--cx:" + mLastLineOffset + "--halfWidth:" + halfWidth);
             pStop.y = bottomY - funnelHeight;
             path.lineTo(pStop.x, pStop.y); //画右边的线
             path.lineTo(pStart.x, pStart.y); //画左边的线
@@ -314,21 +302,22 @@ private void renderPlotDesc(Canvas canvas, float cx, float funnelHeight) {
         }
     }
 
-
-    private float getHalfWidthOffset() {
-        if (count <= 4) {
-            return dip2px(mContext, 17);
-        } else if (count <= 6) {
-            return dip2px(mContext, 13);
-        } else if (count <= 8) {
-            return dip2px(mContext, 10);
-        } else if (count <= 10) {
-            return dip2px(mContext, 7);
+    //画线和字
+    private void renderLabels(Canvas canvas, IFunnelData data, float cx, float y, int color, int halfWidth, int i) {
+        if (data == null) return;
+        mPaintLabelLine.setColor(color);
+        float lineX = cx + halfWidth + mLineWidth;
+        canvas.drawLine(cx, y, lineX, y, mPaintLabelLine);
+        float labelX = lineX + mLineTextSpace;
+        float labelY = y + getPaintFontHeight(mPaintLabel) / 3;
+        if (mCustomLabelCallback == null) {
+            canvas.drawText(data.getLabel(), labelX, labelY, mPaintLabel);
         } else {
-            return dip2px(mContext, 5);
+            mCustomLabelCallback.drawText(canvas, mPaintLabel, labelX, labelY, i);
         }
     }
 
+
     /*
      * 设置线的宽度
      * */
@@ -346,25 +335,67 @@ public void setLineTextSpace(float mLineTextSpace) {
     /*
      * 自定义描述绘制
      * */
-    public void addCustomDrawLabel(CustomLabel iCustomLabel) {
-        this.mCustomLabel = iCustomLabel;
+    public void addCustomLabelCallback(CustomLabelCallback iCustomLabelCallback) {
+        this.mCustomLabelCallback = iCustomLabelCallback;
     }
 
-    //画线和字
-    private void renderLabels(Canvas canvas, IFunnelData data, float cx, float y, int color, int halfWidth, int i) {
-        if (data == null) return;
-        mPaintLabelLine.setColor(color);
-        float lineX = cx + halfWidth + mLineWidth;
-        canvas.drawLine(cx, y, lineX, y, mPaintLabelLine);
-        float labelX = lineX + mLineTextSpace;
-        float labelY = y + getPaintFontHeight(mPaintLabel) / 3;
-        if (mCustomLabel == null) {
-            canvas.drawText(data.getLabel(), labelX, labelY, mPaintLabel);
-//            float labelWidth = DrawHelper.getInstance().getTextWidth(mPaintLabel, data.getLabel());
-//            setNumUi();
-//            canvas.drawText(data.getNumUnit(), labelX + labelWidth, labelY, mPaintLabel);
+    /**
+     * 设置数据源
+     *
+     * @param chartData 数据源
+     */
+    public <T extends IFunnelData> void setChartData(@NonNull List<T> chartData) {
+        setChartData(chartData, null);
+    }
+
+
+    /**
+     * 设置数据源及宽度策略
+     *
+     * @param chartData 数据源
+     * @param callback  宽度策略回调
+     */
+    public <T extends IFunnelData> void setChartData(@NonNull List<T> chartData, HalfWidthCallback callback) {
+        this.mDataSet = (List<IFunnelData>) chartData;
+        this.mHalfWidthCallback = callback;
+        count = mDataSet.size();
+        if (callback == null) {
+            mTopMaxLineHalf = mLastLineOffset + getDefaultHalfWidthOffset() * count;
         } else {
-            mCustomLabel.drawText(canvas, mPaintLabel, labelX, labelY, i);
+            halfArrays = new float[count];
+            float max = 0;
+            float halfWidth = 0;
+            //将所有的宽度数据组装进数组中
+            for (int i = 0; i < count; i++) {
+                halfWidth = callback.getHalfStrategy(halfWidth, count, i);
+                halfArrays[i] = halfWidth;
+            }
+            //找出其中的最大值,此值也就是漏斗的最大宽度(不包括线和描述文字)
+            for (float value : halfArrays) {
+                if (max < value) {
+                    max = value;
+                }
+            }
+            mTopMaxLineHalf = max + mLastLineOffset;
+        }
+        invalidate();
+    }
+
+    /*
+     * 默认漏斗宽度变化策略
+     * */
+    private float getDefaultHalfWidthOffset() {
+        if (count <= 4) {
+            return dip2px(mContext, 17);
+        } else if (count <= 6) {
+            return dip2px(mContext, 13);
+        } else if (count <= 8) {
+            return dip2px(mContext, 10);
+        } else if (count <= 10) {
+            return dip2px(mContext, 7);
+        } else {
+            return dip2px(mContext, 5);
         }
     }
+
 }
diff --git a/funnellib/src/main/java/jie/com/funnellib/HalfWidthCallback.java b/funnellib/src/main/java/jie/com/funnellib/HalfWidthCallback.java
new file mode 100644
index 0000000..e41e47f
--- /dev/null
+++ b/funnellib/src/main/java/jie/com/funnellib/HalfWidthCallback.java
@@ -0,0 +1,16 @@
+package jie.com.funnellib;
+
+/**
+ * Created by hj on 2019/2/25.
+ * 说明:自定义漏斗宽度变化策略
+ */
+public interface HalfWidthCallback {
+    /**
+     * 自定义漏斗宽度变化策略
+     * @param halfWidth 上一个梯形的宽度,第一个为0
+     * @param count 漏斗梯形个数
+     * @param i 梯形下标
+     * @return 当前梯形的宽度,单位:PX
+     */
+    float getHalfStrategy(float halfWidth,int count,int i);
+}
diff --git a/funnellib/src/main/java/jie/com/funnellib/IFunnelData.java b/funnellib/src/main/java/jie/com/funnellib/IFunnelData.java
index a346891..758d347 100644
--- a/funnellib/src/main/java/jie/com/funnellib/IFunnelData.java
+++ b/funnellib/src/main/java/jie/com/funnellib/IFunnelData.java
@@ -2,10 +2,19 @@
 
 /**
  * Created by hj on 2019/2/22.
- * 说明:
+ * 说明:获取漏斗数据源接口
  */
 public interface IFunnelData {
-    String getValue();
+
+    /**
+     * 颜色
+     * @return color
+     */
     int getColor();
+
+    /**
+     * 描述
+     * @return string
+     */
     String getLabel();
 }
diff --git a/funnellib/src/main/java/jie/com/funnellib/Util.java b/funnellib/src/main/java/jie/com/funnellib/Util.java
index 5e3e9bd..77c289f 100644
--- a/funnellib/src/main/java/jie/com/funnellib/Util.java
+++ b/funnellib/src/main/java/jie/com/funnellib/Util.java
@@ -11,18 +11,18 @@
  * Created by hj on 2019/2/22.
  * 说明:
  */
-class Util {
+public class Util {
     /**
      * convert sp to its equivalent px
      * <p>
      * 将sp转换为px
      */
-    static float sp2px(Context context, float spValue) {
+    public static float sp2px(Context context, float spValue) {
         return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue,context.getResources().getDisplayMetrics());
     }
 
     /** dip转换px */
-    static float dip2px(Context context,int dip) {
+    public static float dip2px(Context context,int dip) {
         return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,context.getResources().getDisplayMetrics());
     }
 
@@ -30,7 +30,7 @@ static float dip2px(Context context,int dip) {
     /*
      * 禁止硬件加速
      * */
-    static void disableHardwareAccelerated(View view) {
+    public static void disableHardwareAccelerated(View view) {
         if (view == null) {
             return;
         }
@@ -46,12 +46,24 @@ static void disableHardwareAccelerated(View view) {
      * @param paint 画笔
      * @return 高度
      */
-    static float getPaintFontHeight(Paint paint)
+    public static float getPaintFontHeight(Paint paint)
     {
         Paint.FontMetrics fm = paint.getFontMetrics();
         return (float) Math.ceil(fm.descent - fm.ascent);
     }
 
+    /**
+     * 得到一段文本的宽度
+     * @param paint 画笔
+     * @param str 文本
+     * @return 文本宽度
+     */
+    public static float getTextWidth(Paint paint, String str)
+    {
+        if(str.length() == 0) return 0.0f;
+        return paint.measureText(str, 0, str.length());
+    }
+
     /**
      * 减法运算
      *
@@ -59,7 +71,7 @@ static float getPaintFontHeight(Paint paint)
      * @param v2 参数2
      * @return 运算结果
      */
-    static float sub(float v1, float v2) {
+    public static float sub(float v1, float v2) {
         BigDecimal bgNum1 = new BigDecimal(Float.toString(v1));
         BigDecimal bgNum2 = new BigDecimal(Float.toString(v2));
         return bgNum1.subtract(bgNum2).floatValue();