一、最终效果

wKiom1YngPeDAQAMAA9uevdkEUg286.gif

二、功能分析与实现

 1.LisetView布局

  分析:同样的字母开头,第一个上方有该字母的标志

  实现:在item布局中除了TextView再在其上方加一个TextView等布局(用于显示数据的首字母),然后在适配器的getView中判断该布局是否显示。默认设置该布局的Visibility为VISIBLE,当该布局的的文字内容首字母与上一个不同时说明该item是新的首字母开头,设置为VISIBLE,否则设置为GONE。

部分代码:

            // 默认显示 为了显示第一个布局以及布局复用出现的问题

vh.cd_lable.setVisibility(View.VISIBLE);

String now_city = Cheeses.sCheeseStrings[position];

vh.tv_city.setText(now_city);

String now_letter = now_city.substring(0, 1).toUpperCase();

vh.tv_label.setText(now_letter);

if (position != 0) {

// 与上一个比较 如果不同说明为该字母段的第一个

String pre_letter = Cheeses.sCheeseStrings[position - 1]

.substring(0, 1).toUpperCase();

if (!pre_letter.equals(now_letter)) {

vh.cd_lable.setVisibility(View.VISIBLE);

} else {

vh.cd_lable.setVisibility(View.GONE);


}

}

 2.自定义控件字母索引 

    1.新建一个类继承View,重写两个参数的构造方法,为了获取AttributeSet。重写onDraw方法。


    2.画文字A-Z

     1)调用canvas的drawText(String text, float x, float y, Paint paint)方法用来画A-Z,这里的参数分别代表需要画的文字,起始位置坐标(x,y),画笔。我们先在构造方法里new Paint(Paint.ANTI_ALIAS_FLAG),这里的参数是为了抗锯齿。

    2)画笔有了还需要坐标和文字大小,构造方法里的AttributeSet参数就有作用了。我们在res/values目录下建一个attrs.xml文件,为了放一些我们自定义控件所需要的属性。如下:

<resources>


    <declare-styleable name="LetterSlideView">

        <attr name="textsize" format = "dimension|reference"  />

        <attr name="backcolor" format = "color|reference"  />

        <attr name="frontcolor" format = "color|reference"  />

    </declare-styleable>


</resources>

name一般为你自定义控件的类名,下面属性意思就是名字不多说了,format是为了限定你输入的内容格式,dimension|reference就是只能sp dp或者资源文件之类的。然后再自定义控件类的构造方法中获取这些属性的值:

          TypedArray typedArray = context.obtainStyledAttributes(attrs,

R.styleable.LetterSlideView);

text_size = typedArray.getDimension(

R.styleable.LetterSlideView_textsize, 26);

back_color = typedArray.getColor(R.styleable.LetterSlideView_backcolor,

Color.BLACK);

front_color = typedArray.getColor(

R.styleable.LetterSlideView_frontcolor, Color.RED);

typedArray.recycle();

这时有了文字的大小我们就可以计算我们要画的坐标了,使用paint.measureText(letter);方法可以计算传入文字letter的宽度,x轴的坐标即为

                float letter_w = paint.measureText(letter);

letter_x = (int) ((getWidth() - letter_w) / 2);

getWidth()方法获得控件的总宽度,(总宽度-文字宽度)/2作为起点的x坐标可以让该文字画在控件的正中间。因为我们需要从A画到Z所以写个26大小的for循环,每次y轴+一个文字的高度就可以了,为了让26个字母平均在整个空间的高度则用控件总高度getHeight()/26作为文字的高度。代码如下:

    

                String letter = (char) ('A' + i) + "";

letter_h = getHeight() / 26;

float letter_w = paint.measureText(letter);

letter_x = (int) ((getWidth() - letter_w) / 2);

canvas.drawText(letter, letter_x, letter_h * (i + 1), paint);


    3.动画效果,按下控件有半透明背景以及点击的文字变色。

        1)重写onTouchEvent方法获得触摸事件,按下的时候,文字变色,出现背景。松开的时候回归原状。

        2)文字变色实现:调用event.getY()方法获取触摸事件的高度除以文字的高度这可以获得是第几个字母为成员变量index赋值,松开的时候设为-1。然后再画字的循环中判断是否是该字母并调用paint的setColor方法。

        3)出现背景:当按下的时候为成员变量isPressed赋值原理同上,调用canvas.drawColor方法设置背景。

        4)触摸事件结束时调用invalidate()方法,系统为执行onDraw()方法,注意最后要return ture;表示该事件由我们处理了。

    4.自定义控件控制ListvView的显示。

        1)自定义接口为了实现回调,类似于其他控件的setOnclick机制,写一个public的方法用来获取接口,在触摸事件中调用接口的方法(判断非空)暴露该空间的index值。

接口:

public interface OnLSVTouchListener {

public void getState(int index);

}

方法:

public void setOnLSVTouchListener(OnLSVTouchListener listener) {

this.listener = listener;

}

暴露控件的index:

        if (listener != null) {

listener.getState(index);

}

         

        2)在activity中find该控件并实现该接口,这时候我们就获取到了index也就是第几个英文字母,这时候就需要知道我们的数据中那些数据是同样首字母的第一个。

letter_list.add(0);

for (int i = 0; i < Cheeses.sCheeseStrings.length; i++) {

    if (i + 1 < Cheeses.sCheeseStrings.length) {

    if (!Cheeses.sCheeseStrings[i].substring(0,1)

        .equals(Cheeses.sCheeseStrings[i + 1].substring(0,1))) {

letter_list.add(i + 1);

}

}

}

第一个数据肯定符合要求,然后就是把当前的数据首字母与后一个比较不一样则保存后一个数据进我们的new的容器中(代码没优化)。

        3)实现控制Listview,调用listview的setSelection()方法,传来的index相当于就是我们保存的容器的index,获取到position设置就好了。中间显示的TextView同理。

三、布局使用自定义控件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    xmlns:app="http://schemas.android.com/apk/res/com.example.customview"

    android:id="@+id/FrameLayout1"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="com.example.ui_day2_city_customview.MainActivity" >


    <ListView

        android:id="@+id/lv_city"

        android:layout_width="match_parent"

        android:layout_height="match_parent" >

    </ListView>


    <TextView

        android:id="@+id/tv_city_letter"

        android:layout_width="120dp"

        android:layout_height="120dp"

        android:layout_gravity="center"

        android:background="#80000000"

        android:textSize="80sp"

        android:textColor="#FFFFFF"

        android:gravity="center"

        android:visibility="gone" />


    <com.example.customview.LetterSlideView

        android:id="@+id/lsv_city"

        android:layout_width="30dp"

        android:layout_height="match_parent"

        android:layout_gravity="right" >

    </com.example.customview.LetterSlideView>


</FrameLayout>

注意自定义命名空间、帧布局的位置用android:layout_gravity调整,自定义控件需要完整的包名

注:比较懒,不要在意细节。