贝塞尔曲线字母索引

本文为转载文章  http://blog.csdn.net/zjws23786/article/details/51692774


现在讲一下今天要完成和前面android中字母导航和PinnedHeaderListView(listview头部固定)功能差不多,今天实现的功能是上篇的另一种实现方式,有兴趣的可以看一下

千言万语抵不过一张效果图:



上面右侧字母效果图有没有眼前一亮,如果没接触过这方面的童鞋,都不知道从哪里入手,下面一起来看一下,上面右侧字母的效果图是怎样实现的

讲一下滑动字母里面相关效果

1、有明显字体大小变化

2、字母有明显渐变效果

3、字母在指定的区域内左右滑动,字母有明显伸缩变化


相关技术

主要使用贝塞尔曲线二阶函数,运行效果图如下:


二阶贝塞尔曲线公式:


解释一下公式里面变量相关含义

B(t)表示t时间时点的坐标值(比如x值 或 y值)

P0为起点

P1为控制点

P2为终点


结合贝塞尔曲线二阶函数和下面那张图一起来看代码,这个就不难理解了



上图描述是右侧字母效果图运动的整个轨迹,里面都标的比较清楚了,如果上图看不清楚,可以下载下来再看,这个就比较清楚了。结合工程中LetterIndexer这个类理解起来比较容易了,下面是LetterIndexer类代码

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class LetterIndexer extends View {  
  2.   
  3.   
  4.     public interface OnTouchLetterChangedListener {  
  5.         void onTouchLetterChanged(String s, int index);  
  6.   
  7.         void onTouchActionUp(String s);  
  8.     }  
  9.   
  10.     private Context mContext;  
  11.   
  12.     // 向右偏移多少画字符, default 30  
  13.     private float mWidthOffset = 30.0f;  
  14.   
  15.     // 最小字体大小  
  16.     private int mMinFontSize = 24;  
  17.   
  18.     // 最大字体大小  
  19.     private int mMaxFontSize = 48;  
  20.   
  21.     // 提示字体大小  
  22.     private int mTipFontSize = 52;  
  23.   
  24.     // 提示字符的额外偏移  
  25.     private float mAdditionalTipOffset = 20.0f;  
  26.   
  27.     // 贝塞尔曲线控制的高度  
  28.     private float mMaxBezierHeight = 150.0f;  
  29.   
  30.     // 贝塞尔曲线单侧宽度  
  31.     private float mMaxBezierWidth = 240.0f;  
  32.   
  33.     // 贝塞尔曲线单侧模拟线量  
  34.     private int mMaxBezierLines = 32;  
  35.   
  36.     // 列表字符颜色  
  37.     private int mFontColor = 0xffffffff;  //白色  
  38.   
  39.     // 提示字符颜色  
  40. //  int  mTipFontColor = 0xff3399ff;  
  41.     int mTipFontColor = 0xffd33e48//金  
  42.   
  43.   
  44.     private OnTouchLetterChangedListener mListener;  
  45.   
  46.     private String[] constChar = {"#""A""B""C""D""E""F""G""H""I""J""K""L"  
  47.             , "M""N""O""P""Q""R""S""T""U""V""W""X""Y""Z"};  
  48.   
  49.     private int mConLength = 0;  
  50.   
  51.     private int mChooseIndex = -1;  
  52.     private Paint mPaint = new Paint();  
  53.     private PointF mTouch = new PointF();  
  54.   
  55.     private PointF[] mBezier1;  
  56.     private PointF[] mBezier2;  
  57.   
  58.     private float mLastOffset[]; // 记录每一个字母的x方向偏移量, 数字<=0  
  59.   
  60.     private Scroller mScroller;  
  61.     //正在动画  
  62.     private boolean mAnimating = false;  
  63.     //动画的偏移量  
  64.     private float mAnimationOffset;  
  65.   
  66.     //动画隐藏  
  67.     private boolean mHideAnimation = false;  
  68.   
  69.     //手指是否抬起  
  70.     private boolean isUp = false;  
  71.   
  72.     private int mAlpha = 255;  
  73.   
  74.     /** 
  75.      * 控制距离顶部的距离、底部距离 
  76.      */  
  77.     private int paddingTop = 0;  
  78.     private int paddingBottom = 0;  
  79.   
  80.     Handler mHideWaitingHandler = new Handler() {  
  81.   
  82.         @Override  
  83.         public void handleMessage(Message msg) {  
  84.             if (msg.what == 1) {  
  85. //              mScroller.startScroll(0, 0, 255, 0, 1000);  
  86.                 mHideAnimation = true;  
  87.                 mAnimating = false;                                                        //动画mAnimating=false onDraw触发  
  88.                 LetterIndexer.this.invalidate();  
  89.                 return;  
  90.             }  
  91.             super.handleMessage(msg);  
  92.         }  
  93.     };  
  94.   
  95.     public LetterIndexer(Context context, AttributeSet attrs, int defStyle) {  
  96.         super(context, attrs, defStyle);  
  97.         initData(context, attrs);  
  98.     }  
  99.   
  100.     public LetterIndexer(Context context, AttributeSet attrs) {  
  101.         super(context, attrs);  
  102.         initData(context, attrs);  
  103.     }  
  104.   
  105.     public LetterIndexer(Context context) {  
  106.         super(context);  
  107.         initData(nullnull);  
  108.     }  
  109.   
  110.     private void initData(Context context, AttributeSet attrs) {  
  111.         if (context != null && attrs != null) {  
  112.   
  113.             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LetterIndexer, 00);  
  114.   
  115.             mWidthOffset = a.getDimension(R.styleable.LetterIndexer_widthOffset, mWidthOffset);  
  116.             mMinFontSize = a.getInteger(R.styleable.LetterIndexer_minFontSize, mMinFontSize);  
  117.             mMaxFontSize = a.getInteger(R.styleable.LetterIndexer_maxFontSize, mMaxFontSize);  
  118.             mTipFontSize = a.getInteger(R.styleable.LetterIndexer_tipFontSize, mTipFontSize);  
  119.             mMaxBezierHeight = a.getDimension(R.styleable.LetterIndexer_maxBezierHeight, mMaxBezierHeight);  
  120.             mMaxBezierWidth = a.getDimension(R.styleable.LetterIndexer_maxBezierWidth, mMaxBezierWidth);  
  121.             mMaxBezierLines = a.getInteger(R.styleable.LetterIndexer_maxBezierLines, mMaxBezierLines);  
  122.             mAdditionalTipOffset = a.getDimension(R.styleable.LetterIndexer_additionalTipOffset, mAdditionalTipOffset);  
  123.             mFontColor = a.getColor(R.styleable.LetterIndexer_fontColor, mFontColor);  
  124.             mTipFontColor = a.getColor(R.styleable.LetterIndexer_tipFontColor, mTipFontColor);  
  125.             a.recycle();  
  126.         }  
  127.   
  128.   
  129.         this.mContext = context;  
  130.         mScroller = new Scroller(getContext());  
  131.         mTouch.x = 0;  
  132.         mTouch.y = -10 * mMaxBezierWidth;  
  133.   
  134.         mBezier1 = new PointF[mMaxBezierLines];  
  135.         mBezier2 = new PointF[mMaxBezierLines];  
  136.   
  137.         commonData(00);  
  138.     }  
  139.   
  140.     /** 
  141.      * 需 注意的是,传值单位是sp 
  142.      * @param top   距离顶部的距离 
  143.      * @param bottom 距离底部的距离 
  144.      */  
  145.     private void commonData(int top, int bottom) {  
  146.         paddingTop = DisplayUtils.convertDIP2PX(mContext,top);  
  147.         paddingBottom = DisplayUtils.convertDIP2PX(mContext,bottom);  
  148.         mConLength = constChar.length;  
  149.         mLastOffset = new float[mConLength];  
  150.         calculateBezierPoints();  
  151.     }  
  152.   
  153.     public void setConstChar(String[] constChar,int top, int bottom) {  
  154.         this.constChar = constChar;  
  155.         commonData(top,bottom);  
  156.     }  
  157.   
  158.     @Override  
  159.     protected void onDraw(Canvas canvas) {  
  160.   
  161.         // 控件宽高  
  162.         int height = getHeight() - paddingTop - paddingBottom;  
  163.         int width = getWidth();  
  164.   
  165.         // 单个字母高度  
  166.         float singleHeight = height / (float) constChar.length;  
  167.   
  168.         int workHeight = paddingTop;  
  169.   
  170.         if (mAlpha == 0)  
  171.             return;  
  172.   
  173.         //恢复画笔的默认设置。  
  174.         mPaint.reset();  
  175.   
  176.         /** 
  177.          * 遍历所以字母内容 
  178.          */  
  179.         for (int i = 0; i < mConLength; i++) {  
  180.   
  181.             mPaint.setColor(mFontColor);  
  182.             mPaint.setAntiAlias(true);  
  183.   
  184.             float xPos = width - mWidthOffset;           // 字母在 x 轴的位置      基本保持不变  
  185.             float yPos = workHeight + singleHeight / 2;  //字母在 y 轴的位置     该值一直在变化  
  186.   
  187.             // 根据当前字母y的位置计算得到字体大小  
  188.             int fontSize = adjustFontSize(i, yPos);  
  189.             mPaint.setTextSize(fontSize);  
  190.             mAlpha = 255 - fontSize*4;  
  191.             mPaint.setAlpha(mAlpha);  
  192.             if (i == mChooseIndex){  
  193.                 mPaint.setColor(Color.parseColor("#F50527"));  
  194.             }  
  195.   
  196.             // 添加一个字母的高度  
  197.             workHeight += singleHeight;  
  198.             // 绘制字母  
  199.             drawTextInCenter(canvas, constChar[i], xPos + ajustXPosAnimation(i, yPos), yPos);  
  200.   
  201.             // 如果手指抬起  
  202.             if (isUp) {  
  203.                 mListener.onTouchActionUp(constChar[mChooseIndex]);  
  204.                 isUp = false;  
  205.             }  
  206.             mPaint.reset();  
  207.         }  
  208.     }  
  209.   
  210.     /** 
  211.      * @param canvas  画板 
  212.      * @param string  被绘制的字母 
  213.      * @param xCenter 字母的中心x方向位置 
  214.      * @param yCenter 字母的中心y方向位置 
  215.      */  
  216.     private void drawTextInCenter(Canvas canvas, String string, float xCenter, float yCenter) {  
  217.   
  218.         FontMetrics fm = mPaint.getFontMetrics();  
  219.         float fontHeight = mPaint.getFontSpacing();  
  220.   
  221.         float drawY = yCenter + fontHeight / 2 - fm.descent;  
  222.   
  223.         if (drawY < -fm.ascent - fm.descent)  
  224.             drawY = -fm.ascent - fm.descent;  
  225.   
  226.         if (drawY > getHeight())  
  227.             drawY = getHeight();  
  228.   
  229.         mPaint.setTextAlign(Align.CENTER);  
  230.   
  231.         canvas.drawText(string, xCenter, drawY, mPaint);  
  232.     }  
  233.   
  234.     private int adjustFontSize(int i, float yPos) {  
  235.   
  236.         // 根据水平方向偏移量计算出一个放大的字号  
  237.         float adjustX = Math.abs(ajustXPosAnimation(i, yPos));  
  238.         int adjustSize = (int) ((mMaxFontSize - mMinFontSize) * adjustX / mMaxBezierHeight) + mMinFontSize;  
  239.   
  240.         return adjustSize;  
  241.     }  
  242.   
  243.     /** 
  244.      * x 方向的向左偏移量 
  245.      * 
  246.      * @param i    当前字母的索引 
  247.      * @param yPos y方向的初始位置  会变化 
  248.      * @return 
  249.      */  
  250.     private float ajustXPosAnimation(int i, float yPos) {  
  251.   
  252.         float offset;  
  253.         if (this.mAnimating || this.mHideAnimation) {  
  254.             // 正在动画中或在做隐藏动画  
  255.             offset = mLastOffset[i];  
  256.             if (offset != 0.0f) {  
  257.                 offset += this.mAnimationOffset;  
  258.                 if (offset > 0)  
  259.                     offset = 0;  
  260.             }  
  261.         } else {  
  262.   
  263.             // 根据当前字母y方向位置, 计算水平方向偏移量  
  264.             offset = adjustXPos(yPos);  
  265.   
  266.             // 当前触摸的x方向位置  
  267.             float xPos = mTouch.x;  
  268.   
  269.             float width = getWidth() - mWidthOffset;  
  270.             width = width - 60;  
  271.   
  272.             // 字母绘制时向左偏移量 进行修正, offset需要是<=0的值  
  273.             if (offset != 0.0f && xPos > width){  
  274.                 offset += (xPos - width);  
  275.             }  
  276.             if (offset > 0){  
  277.                 offset = 0;  
  278.             }  
  279.   
  280.             mLastOffset[i] = offset;  
  281.         }  
  282.         return offset;  
  283.     }  
  284.   
  285.     private float adjustXPos(float yPos) {  
  286.   
  287.         float dis = yPos - mTouch.y; // 字母y方向位置和触摸时y值坐标的差值, 距离越小, 得到的水平方向偏差越大  
  288.         if (dis > -mMaxBezierWidth && dis < mMaxBezierWidth) {  
  289.             // 在2个贝赛尔曲线宽度范围以内 (一个贝赛尔曲线宽度是指一个山峰的一边)  
  290.   
  291.             // 第一段 曲线  
  292.             if (dis > mMaxBezierWidth / 4) {  
  293.                 for (int i = mMaxBezierLines - 1; i > 0; i--) {  
  294.                     // 从下到上, 逐个计算  
  295.   
  296.                     if (dis == -mBezier1[i].y) // 落在点上  
  297.                         return mBezier1[i].x;  
  298.   
  299.                     // 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量  
  300.                     if (dis > -mBezier1[i].y && dis < -mBezier1[i - 1].y) {  
  301.                         return (dis + mBezier1[i].y) * (mBezier1[i - 1].x - mBezier1[i].x) / (-mBezier1[i - 1].y + mBezier1[i].y) + mBezier1[i].x;  
  302.                     }  
  303.                 }  
  304.                 return mBezier1[0].x;  
  305.             }  
  306.   
  307.             // 第三段 曲线, 和第一段曲线对称  
  308.             if (dis < -mMaxBezierWidth / 4) {  
  309.                 for (int i = 0; i < mMaxBezierLines - 1; i++) {  
  310.                     // 从上到下  
  311.   
  312.                     if (dis == mBezier1[i].y) // 落在点上  
  313.                         return mBezier1[i].x;  
  314.   
  315.                     // 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量  
  316.                     if (dis > mBezier1[i].y && dis < mBezier1[i + 1].y) {  
  317.                         return (dis - mBezier1[i].y) * (mBezier1[i + 1].x - mBezier1[i].x) / (mBezier1[i + 1].y - mBezier1[i].y) + mBezier1[i].x;  
  318.                     }  
  319.                 }  
  320.                 return mBezier1[mMaxBezierLines - 1].x;  
  321.             }  
  322.   
  323.             // 第二段 峰顶曲线  
  324.             for (int i = 0; i < mMaxBezierLines - 1; i++) {  
  325.   
  326.                 if (dis == mBezier2[i].y)  
  327.                     return mBezier2[i].x;  
  328.   
  329.                 // 如果距离dis落在两个贝塞尔曲线模拟点之间, 通过三角函数计算得到当前dis对应的x方向偏移量  
  330.                 if (dis > mBezier2[i].y && dis < mBezier2[i + 1].y) {  
  331.                     return (dis - mBezier2[i].y) * (mBezier2[i + 1].x - mBezier2[i].x) / (mBezier2[i + 1].y - mBezier2[i].y) + mBezier2[i].x;  
  332.                 }  
  333.             }  
  334.             return mBezier2[mMaxBezierLines - 1].x;  
  335.   
  336.         }  
  337.   
  338.         return 0.0f;  
  339.     }  
  340.   
  341.     @Override  
  342.     public boolean dispatchTouchEvent(MotionEvent event) {  
  343.         final int action = event.getAction();  
  344.         final float y = event.getY();  
  345.         final int oldmChooseIndex = mChooseIndex;  
  346.         final OnTouchLetterChangedListener listener = mListener;  
  347.         /** 
  348.          * 计算除去paddingTop后,用户点击不同位置对应的字母索引 
  349.          */  
  350.         final int c = (int) ((y-paddingTop) / (getHeight()-paddingTop-paddingBottom) * constChar.length);  
  351.   
  352.   
  353.         switch (action) {  
  354.             case MotionEvent.ACTION_DOWN:  
  355.   
  356.                 if (this.getWidth() > mWidthOffset) {  
  357.                     if (event.getX() < this.getWidth() - mWidthOffset)  
  358.                         return false;  
  359.                 }  
  360.   
  361.                 if (y < paddingTop || c<0 || y > getHeight()-paddingBottom){  
  362.                     return false;  
  363.                 }  
  364.   
  365.                 mHideWaitingHandler.removeMessages(1);  
  366.   
  367.                 mScroller.abortAnimation();  
  368.                 mAnimating = false;  
  369.                 mHideAnimation = false;  
  370.                 mAlpha = 255;  
  371.   
  372.                 mTouch.x = event.getX();  
  373.                 mTouch.y = event.getY();  
  374.   
  375.                 if (oldmChooseIndex != c && listener != null) {  
  376.                     if (c > 0 && c < constChar.length) {  
  377.                         listener.onTouchLetterChanged(constChar[c],c);  
  378.                         mChooseIndex = c;  
  379.                     }  
  380.                 }  
  381.                 invalidate();  
  382.                 break;  
  383.             case MotionEvent.ACTION_MOVE:  
  384.                 mTouch.x = event.getX();  
  385.                 mTouch.y = event.getY();  
  386.                 invalidate();  
  387.                 if (oldmChooseIndex != c && listener != null) {  
  388.   
  389.                     if (c >= 0 && c < constChar.length) {  
  390.                         listener.onTouchLetterChanged(constChar[c],c);  
  391.                         mChooseIndex = c;  
  392.                     }  
  393.                 }  
  394.                 break;  
  395.             case MotionEvent.ACTION_UP:  
  396.                 mTouch.x = event.getX();  
  397.                 mTouch.y = event.getY();  
  398.   
  399.                 isUp = true;  
  400.                 mScroller.startScroll(00, (int) mMaxBezierHeight, 02000);  
  401.                 mAnimating = true;  
  402.                 postInvalidate();  
  403.                 break;  
  404.   
  405.         }  
  406.         return true;  
  407.     }  
  408.   
  409.     @Override  
  410.     public void computeScroll() {  
  411.         super.computeScroll();  
  412.         if (mScroller.computeScrollOffset()) {  
  413.             if (mAnimating) {  
  414.                 float x = mScroller.getCurrX();  
  415.                 mAnimationOffset = x;  
  416.             } else if (mHideAnimation) {  
  417.                 mAlpha = 255 - (int) mScroller.getCurrX();  
  418.             }  
  419.             invalidate();  
  420.         } else if (mScroller.isFinished()) {  
  421.             if (mAnimating) {  
  422.                 mHideWaitingHandler.sendEmptyMessage(1);  
  423.             } else if (mHideAnimation) {  
  424.                 mHideAnimation = false;  
  425.                 this.mChooseIndex = -1;  
  426.                 mTouch.x = -10000;  
  427.                 mTouch.y = -10000;  
  428.             }  
  429.   
  430.         }  
  431.     }  
  432.   
  433.     public void setOnTouchLetterChangedListener(OnTouchLetterChangedListener listener) {  
  434.         this.mListener = listener;  
  435.     }  
  436.   
  437.     /** 
  438.      * 计算出所有贝塞尔曲线上的点 
  439.      * 个数为 mMaxBezierLines * 2 = 64 
  440.      */  
  441.     private void calculateBezierPoints() {  
  442.   
  443.         PointF mStart = new PointF();   // 开始点  
  444.         PointF mEnd = new PointF();        // 结束点  
  445.         PointF mControl = new PointF(); // 控制点  
  446.   
  447.   
  448.         // 计算第一段红色部分 贝赛尔曲线的点  
  449.         // 开始点  
  450.         mStart.x = 0.0f;  
  451.         mStart.y = -mMaxBezierWidth;  
  452.   
  453.         // 控制点  
  454.         mControl.x = 0.0f;  
  455.         mControl.y = -mMaxBezierWidth / 2;  
  456.   
  457.         // 结束点  
  458.         mEnd.x = -mMaxBezierHeight / 2;  
  459.         mEnd.y = -mMaxBezierWidth / 4;  
  460.   
  461.         mBezier1[0] = new PointF();  
  462.         mBezier1[mMaxBezierLines - 1] = new PointF();  
  463.   
  464.         mBezier1[0].set(mStart);  
  465.         mBezier1[mMaxBezierLines - 1].set(mEnd);  
  466.   
  467.         for (int i = 1; i < mMaxBezierLines - 1; i++) {  
  468.   
  469.             mBezier1[i] = new PointF();  
  470.   
  471.             mBezier1[i].x = calculateBezier(mStart.x, mEnd.x, mControl.x, i / (float) mMaxBezierLines);  
  472.             mBezier1[i].y = calculateBezier(mStart.y, mEnd.y, mControl.y, i / (float) mMaxBezierLines);  
  473.   
  474.         }  
  475.   
  476.         // 计算第二段蓝色部分 贝赛尔曲线的点  
  477.         mStart.y = -mMaxBezierWidth / 4;  
  478.         mStart.x = -mMaxBezierHeight / 2;  
  479.   
  480.         mControl.y = 0.0f;  
  481.         mControl.x = -mMaxBezierHeight;  
  482.   
  483.         mEnd.y = mMaxBezierWidth / 4;  
  484.         mEnd.x = -mMaxBezierHeight / 2;  
  485.   
  486.         mBezier2[0] = new PointF();  
  487.         mBezier2[mMaxBezierLines - 1] = new PointF();  
  488.   
  489.         mBezier2[0].set(mStart);  
  490.         mBezier2[mMaxBezierLines - 1].set(mEnd);  
  491.   
  492.         for (int i = 1; i < mMaxBezierLines - 1; i++) {  
  493.   
  494.             mBezier2[i] = new PointF();  
  495.             mBezier2[i].x = calculateBezier(mStart.x, mEnd.x, mControl.x, i / (float) mMaxBezierLines);  
  496.             mBezier2[i].y = calculateBezier(mStart.y, mEnd.y, mControl.y, i / (float) mMaxBezierLines);  
  497.         }  
  498.     }  
  499.   
  500.     /** 
  501.      * 贝塞尔曲线核心算法 
  502.      * 
  503.      * @param start 
  504.      * @param end 
  505.      * @param control 
  506.      * @param val 
  507.      * @return 公式及动图, 维基百科: https://en.wikipedia.org/wiki/B%C3%A9zier_curve 
  508.      * 中文可参考此网站: http://blog.csdn.net/likendsl/article/details/7852658 
  509.      */  
  510.     private float calculateBezier(float start, float end, float control, float val) {  
  511.   
  512.         float t = val;  
  513.         float s = 1 - t;  
  514.   
  515.         float ret = start * s * s + 2 * control * s * t + end * t * t;  
  516.   
  517.         return ret;  
  518.     }  
  519. }  

上面代码是实现字母右侧滑动效果的核心类,里面都有注释了,记得这个类要结合上图来看,会发现哦原来是这样子,很溜吧。所以学好数学是很必要的


attrs.xml文件内容

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="LetterIndexer">  
  4.         <attr name="widthOffset" format="dimension" />  
  5.         <attr name="minFontSize" format="integer" />  
  6.         <attr name="maxFontSize" format="integer" />  
  7.         <attr name="tipFontSize" format="integer" />  
  8.         <attr name="maxBezierHeight" format="dimension" />  
  9.         <attr name="maxBezierWidth" format="dimension" />  
  10.         <attr name="maxBezierLines" format="integer" />  
  11.         <attr name="additionalTipOffset" format="dimension" />  
  12.         <attr name="fontColor" format="color" />  
  13.         <attr name="tipFontColor" format="color" />  
  14.     </declare-styleable>  
  15.   
  16. </resources>  


主MainActivity

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends Activity {  
  2.     private PinnedHeaderListView pinnedHeaderListView;  
  3.     private ArrayList<Person> persons;                                //英雄好汉列表数据集 (除过分组标签,列表所有数据,无序)  
  4.     private LinkedHashMap<String, List<Person>> personMpas;           //英雄好汉列表 分组标签对应的数据集合(有序)  
  5.     private PinnedHeaderListViewAdapter<Person> adapter;              //英雄好汉列表适配器  
  6.     private LetterIndexer letterIndexer;  
  7.     private TextView tv_index_center;  
  8.     private Handler mHandler = new Handler();  
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.         initView();  
  15.         lister();  
  16.         //初始化数据  
  17.         initData();  
  18.     }  
  19.   
  20.     private void initView() {  
  21.         pinnedHeaderListView = (PinnedHeaderListView)findViewById(R.id.pinnedheader_listview);  
  22.         pinnedHeaderListView.setPinnedHeaderView(this.getLayoutInflater().inflate(  
  23.                 R.layout.pinnedheaderlistview_header_layout, pinnedHeaderListView, false));  
  24.         letterIndexer = (LetterIndexer) findViewById(R.id.letter_index);  
  25.         tv_index_center = (TextView) findViewById(R.id.tv_index_center);  
  26.     }  
  27.   
  28.     private void lister() {  
  29.         letterIndexer.setOnTouchLetterChangedListener(new LetterIndexer.OnTouchLetterChangedListener() {  
  30.   
  31.             @Override  
  32.             public void onTouchLetterChanged(String letter,int index) {  
  33.   
  34.                 // 从集合中查找第一个拼音首字母为letter的索引, 进行跳转  
  35.                 for (int i = 0; i < persons.size(); i++) {  
  36.                     Person person = persons.get(i);  
  37.                     String s = person.getLetter();  
  38.                     if(TextUtils.equals(s, letter)){  
  39.                         // 匹配成功, 中断循环, 将列表移动到指定的位置  
  40.                         pinnedHeaderListView.setSelection(i);  
  41.                         break;  
  42.                     }  
  43.                 }  
  44.             }  
  45.   
  46.             @Override  
  47.             public void onTouchActionUp(String letter) {  
  48.                 showLetter(letter);  
  49.             }  
  50.         });  
  51.     }  
  52.   
  53.     /** 
  54.      * 显示字母提示 
  55.      * 
  56.      * @param letter 
  57.      */  
  58.     protected void showLetter(String letter) {  
  59.         tv_index_center.setVisibility(View.VISIBLE);  
  60.         tv_index_center.setText(letter);  
  61.   
  62.         mHandler.removeCallbacksAndMessages(null);  
  63.         mHandler.postDelayed(new Runnable() {  
  64.             @Override  
  65.             public void run() {  
  66.                 // 隐藏  
  67.                 tv_index_center.setVisibility(View.GONE);  
  68.             }  
  69.         }, 2000);  
  70.   
  71.     }  
  72.   
  73.     protected void initData() {  
  74.         InternalStorageUtils.asynReadInternalFile(this"names.config"new AsyncResonseHandler() {  
  75.             @Override  
  76.             protected void onSuccess(String content) {  
  77.                 super.onSuccess(content);  
  78.                 try {  
  79.                     Gson gson = new Gson();  
  80.                     HeroPerson hero = gson.fromJson(content,  
  81.                             new TypeToken<HeroPerson>() {  
  82.                             }.getType());  
  83.   
  84.                     List<PersonList> personList = hero.getSections();  
  85.                     persons = new ArrayList<Person>();  
  86.                     personMpas = new LinkedHashMap<String, List<Person>>();  
  87.   
  88.                     //特殊字符  
  89.                     List<Person> specialChar = new ArrayList<Person>();  
  90.                     Person charPerson = null;  
  91.                     for (int i=0; i<5; i++){  
  92.                         charPerson = new Person();  
  93.                         charPerson.setName("#特殊字符"+i);  
  94.                         charPerson.setLetter("#");  
  95.                         specialChar.add(charPerson);  
  96.                     }  
  97.                     personMpas.put("特殊字符", specialChar);  
  98.                     persons.addAll(specialChar);  
  99.   
  100.                     //得到右侧字母索引的内容  
  101.                     int letterLength = personList.size()+ personMpas.size();  
  102.                     String[] constChar = new String[letterLength];  
  103.                     constChar[0] = "#";  
  104.   
  105.   
  106.                     List<Person> personItems;  
  107.                     for (int i = 0; i < personList.size(); i++) {  
  108.                         personItems = personList.get(i).getPersons();  
  109.                         persons.addAll(personItems);  
  110.                         personMpas.put(personList.get(i).getIndex(), personItems);  
  111.                         constChar[i+1] = personList.get(i).getIndex();  
  112.                     }  
  113.   
  114.                     adapter = new PinnedHeaderListViewAdapter<Person>(MainActivity.this, personMpas, pinnedHeaderListView,  
  115.                             letterIndexer,constChar, 2020);  
  116.                     pinnedHeaderListView.setOnScrollListener(adapter);  
  117.                     pinnedHeaderListView.setAdapter(adapter);  
  118.                 } catch (Exception e) {  
  119.                     e.printStackTrace();  
  120.                 }  
  121.             }  
  122.         });  
  123.     }  
  124.   
  125. }  

activity_main.xml

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:huahua="http://schemas.android.com/apk/res-auto"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     android:background="@color/bg_gray_main" >  
  8.     <TextView  
  9.         android:id="@+id/tv_title"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="50dp"  
  12.         android:gravity="center"  
  13.         android:background="#96B1B4"  
  14.         android:text="标题内容"/>  
  15.   
  16.     <RelativeLayout  
  17.         android:layout_width="match_parent"  
  18.         android:layout_height="0dp"  
  19.         android:layout_weight="1">  
  20.   
  21.         <include layout="@layout/person_pinned_header_listview"  
  22.             android:id="@+id/pinnedheader_listview"/>  
  23.   
  24.         <cn.com.huahua.pinnedheaderlistview.ui.LetterIndexer  
  25.             android:id="@+id/letter_index"  
  26.             android:layout_width="match_parent"  
  27.             android:layout_height="match_parent"  
  28.             huahua:widthOffset="15dip"  
  29.             huahua:minFontSize="32"  
  30.             huahua:maxFontSize="77"  
  31.             huahua:tipFontSize="72"  
  32.             huahua:maxBezierHeight="150dip"  
  33.             huahua:maxBezierWidth="180dip"  
  34.             huahua:additionalTipOffset="40dip"  
  35.             huahua:fontColor="#2278BF" />  
  36.   
  37.         <TextView  
  38.             android:id="@+id/tv_index_center"  
  39.             android:visibility="gone"  
  40.             android:layout_width="100dp"  
  41.             android:layout_height="100dp"  
  42.             android:layout_centerInParent="true"  
  43.             android:textColor="#FFFFFF"  
  44.             android:gravity="center"  
  45.             android:textSize="36sp"  
  46.             android:background="@drawable/alpha_center_corner"  
  47.             android:text="A" />  
  48.     </RelativeLayout>  
  49. </LinearLayout>  


下面提供一下源码

源码下载


参考资料

http://blog.csdn.NET/likendsl/article/details/7852658

http://www.jcodecraeer.com/a/opensource/2015/1104/3656.html



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值