文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android通用索引栏实现代码

2022-06-06 05:25

关注

偶尔看到之前写过的代码,感觉好多东西几乎在很多项目中都要用到,虽然每个项目的需求和设计都不同,不过实现的效果都是一样的,可能只是数据格式和一些颜色等的细微差距.但是有的时候因为一个小改变,就要去重复的修改代码,麻烦不说,也容易导致新的问题和BUG.

就拿忽然想到的索引栏来说,几乎写过的项目中都用到了,比如城市选择、联系人等等.这些地方全都需要用到索引栏,但是用法都是一样的.翻看了几处之前写过的代码,发现每次用到索引栏,都要重新去写方法来处理数据或者对数据的索引进行提取这些,做法也都大同小异.于是乎,尝试着重构一下这部分,也方便之后的使用.

先看一下效果图:

实现

索引栏的实现,网上有很多例子,也比较简单,就不做过多解释.因为在不同项目中可能涉及到索引栏字体颜色、大小不同等问题,所以把之前用到的代码做一下修改,提取出一些自定义属性,方便修改,就不必每次都去代码中修改,也避免影响到其他人的使用.直接看一下代码,在attr中定义一些自定义属性,如下:

attr:


 <?xml version="1.0" encoding="utf-8"?>
<resources>
  <!--SideBar相关-->
  <!--普通时的颜色-->
  <attr name="normalColor" format="color"/>
  <!--选中时的颜色-->
  <attr name="chooseColor" format="color"/>
  <!--普通时的背景图-->
  <attr name="normalBackground" format="reference"/>
  <!--选中时的背景图-->
  <attr name="chooseBackground" format="reference"/>
  <!--索引栏文字大小-->
  <attr name="sideTextSize" format="dimension"/>
  <declare-styleable name="SideBar">
    <attr name="normalColor"/>
    <attr name="chooseColor"/>
    <attr name="normalBackground"/>
    <attr name="chooseBackground"/>
    <attr name="sideTextSize"/>
  </declare-styleable>
</resources>

把颜色文字大小等属性提取出来方便修改.然后看一下SideBar

SideBar:


package com.example.junweiliu.commindexdemo.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.example.junweiliu.commindexdemo.R;

public class SideBar extends View {
  
  private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
  
  public static String[] letterStrs = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
      "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
      "W", "X", "Y", "Z", "#"};
  
  private int choose = -1;
  
  private Paint paint = new Paint();
  
  private TextView mTextDialog;
  
  private int normalColor;
  
  private int chooseColor;
  
  private Drawable normalBackground;
  
  private Drawable chooseBackground;
  
  private float textSize;
  
  private Rect mRect;
  public SideBar(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
  public SideBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // 获取自定义属性
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SideBar);
    normalColor = ta.getColor(R.styleable.SideBar_normalColor, Color.GRAY);
    chooseColor = ta.getColor(R.styleable.SideBar_chooseColor, Color.RED);
    normalBackground = ta.getDrawable(R.styleable.SideBar_normalBackground);
    chooseBackground = ta.getDrawable(R.styleable.SideBar_chooseBackground);
    textSize = ta.getDimension(R.styleable.SideBar_sideTextSize, TypedValue
        .applyDimension(TypedValue.COMPLEX_UNIT_SP, 13,
            getResources().getDisplayMetrics()));
    ta.recycle();
    init();
  }
  
  public void setTextView(TextView mTextDialog) {
    this.mTextDialog = mTextDialog;
  }
  
  public void setLetter(String[] letter) {
    this.letterStrs = letter;
    invalidate();
    requestLayout();
  }
  
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void init() {
    paint.setColor(normalColor);
    paint.setTypeface(Typeface.DEFAULT_BOLD);
    paint.setAntiAlias(true);
    paint.setTextSize(textSize);
    // 获取单个绘制的rect,用于获取单个绘制项的高度
    mRect = new Rect();
    paint.getTextBounds("A", 0, "A".length(), mRect);
  }
  
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 获取焦点改变背景颜色.
    int height = getHeight() - getPaddingTop() - getPaddingBottom();// 获取对应高度
    int width = getWidth(); // 获取对应宽度
    int singleHeight = height / letterStrs.length;// 获取每一个字母的高度
    for (int i = 0; i < letterStrs.length; i++) {
      // 选中的状态
      if (i == choose) {
        paint.setColor(chooseColor);
        paint.setFakeBoldText(true);
      }
      // x坐标等于中间-字符串宽度的一半.
      float xPos = width / 2 - paint.measureText(letterStrs[i]) / 2;
      float yPos = singleHeight * i + singleHeight;
      canvas.drawText(letterStrs[i], xPos, yPos, paint);
      paint.reset();// 重置画笔
      init();
    }
  }
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  @Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    final int action = event.getAction();
    // 点击的y坐标
    final float y = event.getY();
    final int oldChoose = choose;
    final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
    // 获取当前点击的字母位置,点击位置的y坐标比上总的高度相当于点击的位置比上全部位置(c / b.length = y / getHeight())
    final int currChoose = (int) (y / getHeight() * letterStrs.length);
    switch (action) {
      case MotionEvent.ACTION_UP:
        // 重置背景色
        if (null != normalBackground) {
          setBackground(normalBackground);
        } else {
          setBackgroundColor(Color.argb(0, 0, 0, 0));
        }
        // 抬起时置为-1
        choose = -1;
        invalidate();
        if (mTextDialog != null) {
          mTextDialog.setVisibility(View.INVISIBLE);
        }
        break;
      default:
        // 设置背景色
        if (null != chooseBackground) {
          setBackground(chooseBackground);
        }
        if (oldChoose != currChoose) {
          if (currChoose >= 0 && currChoose < letterStrs.length) {
            if (null != listener) {
              listener.onTouchingLetterChanged(letterStrs[currChoose]);
            }
            if (null != mTextDialog) {
              mTextDialog.setText(letterStrs[currChoose]);
              mTextDialog.setVisibility(View.VISIBLE);
            }
            // 设置选中的位置为当前位置
            choose = currChoose;
            invalidate();
          }
        }
        break;
    }
    return true;
  }
  
  public void setOnTouchingLetterChangedListener(
      OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
    this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
  }
  
  public interface OnTouchingLetterChangedListener {
    void onTouchingLetterChanged(String s);
  }
  
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
    // 当高度为自适应时,高度为字母高度*字母数量*2 即间隔为单位高度
    int wrapHeight = letterStrs.length * (mRect.height() * 2);
    // 当宽度为自适应使,宽度为字母宽度*2
    int warpWidth = mRect.width() * 2;
    setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : warpWidth
        , (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
            //wrap_content时的高度
            : wrapHeight);
  }
}

很简单,只是提取出来了一些自定义属性,没什么可说的,接下来分析一下如何让索引栏变得通用,先来想一下索引栏一般的写法.首先拿到一个数据源,然后对这个数据源进行处理,从数据源中提取出首字母当做索引(当然数据源中可能已经含有首字母或者索引等字段),有了索引之后,再在适配器中进行判断来控制是否显示索引标题(我的做法是判断第一次出现当前索引的数据源位置和当前位置是否相同,如果相同则显示索引标题),处理完索引标题的显示和隐藏,最后就是跟索引栏进行绑定(实现索引栏的回调方法并做相关处理).大体步骤就是这样,接下来就是找一下处理不同的地方,比对了一下,发现问题基本都是出现在数据格式不同上,有的数据的索引字段可能叫Letter,有的可能叫LetterName,这就导致了每次对这些数据进行处理时,都要重新写方法或者修改方法,使得这些方法不共用.那怎么解决一下这个问题呢,最开始想到的是写一个抽象类,然后用一个抽象方法getLetterName()来约束索引.每个需要用到索引栏的Bean都去继承这个抽象类,重写这个抽象方法,从而达到统一约束索引值的效果,也就解决了索引值字段不同的问题,这样就可以用一个公共的方法来处理不同的数据源.后来又考虑了一下,这个地方其实用接口会更加合适一点,接口灵活性更大,而且也是面向接口编程的一种体现.分析了这么多,来具体代码实现一下,提出一个接口,之后所有需要用到索引的数据Bean去实现这个接口中的getLetterName()方法并且重写这个方法来返回索引值即可.

接口SideBase:


package com.example.junweiliu.commindexdemo.bean;

public interface SideBase {
  String getLetterName();
}

然后数据Bean去实现这个接口,例如比较常见的CityBean:


package com.example.junweiliu.commindexdemo.bean;
public class CityBean implements SideBase {
  
  private String cityName;
  
  private String cityHeader;
  
  private String cityMes;
  public CityBean(String cityName, String cityHeader, String cityMes) {
    this.cityName = cityName;
    this.cityHeader = cityHeader;
    this.cityMes = cityMes;
  }
  public String getCityName() {
    return cityName;
  }
  public void setCityName(String cityName) {
    this.cityName = cityName;
  }
  public String getCityHeader() {
    return cityHeader;
  }
  public void setCityHeader(String cityHeader) {
    this.cityHeader = cityHeader;
  }
  public String getCityMes() {
    return cityMes;
  }
  public void setCityMes(String cityMes) {
    this.cityMes = cityMes;
  }
  
  @Override
  public String getLetterName() {
    return cityHeader;
  }
}

在getLetterName()方法中去返回索引值.

接下来就可以去写一些公共的处理方法.比如


  
  public static int getPositionForSection(int position, List<? extends SideBase> datas) {
    // 当前选中的项
    SideBase sideBase = datas.get(position);
    for (int i = 0; i < datas.size(); i++) {
      String firstStr = datas.get(i).getLetterName().toUpperCase();
      // 返回第一次出现该项首字母的位置
      if (firstStr.equals(sideBase.getLetterName())) {
        return i;
      }
    }
    return -1;
  }

因为使用的接口,这里就可以用通配符?的方式来对数据进行处理,只关心和处理getLetterName()方法即可.还可以做其他处理:


  
  public static int getLetterPosition(List<? extends SideBase> list, String letter) {
    int position = -1;
    if (list != null && !list.isEmpty() && !"".equals(letter)) {
      for (int i = 0; i < list.size(); i++) {
        SideBase bean = list.get(i);
        if (bean.getLetterName().equals(letter)) {
          position = i;
          break;
        }
      }
    }
    return position;
  }
  
  public static String[] getLetters(List<? extends SideBase> list) {
    List<String> letters = new ArrayList<>();
    if (list != null && !list.isEmpty()) {
      for (int i = 0; i < list.size(); i++) {
        if (!letters.contains(list.get(i).getLetterName())) {
          letters.add(list.get(i).getLetterName());
        }
      }
    }
    return (String[]) letters.toArray(new String[letters.size()]);
  }

通过分析和重构之后,这些之前不能公用的方法就变得通用起来,很方便,之后用起来也会特别简单、舒心.

完整代码

公共处理类

CommUtil:


package com.example.junweiliu.commindexdemo.util;
import com.example.junweiliu.commindexdemo.bean.SideBase;
import java.util.ArrayList;
import java.util.List;

public class CommUtil {
  
  public static int getPositionForSection(int position, List<? extends SideBase> datas) {
    // 当前选中的项
    SideBase sideBase = datas.get(position);
    for (int i = 0; i < datas.size(); i++) {
      String firstStr = datas.get(i).getLetterName().toUpperCase();
      // 返回第一次出现该项首字母的位置
      if (firstStr.equals(sideBase.getLetterName())) {
        return i;
      }
    }
    return -1;
  }
  
  public static int getLetterPosition(List<? extends SideBase> list, String letter) {
    int position = -1;
    if (list != null && !list.isEmpty() && !"".equals(letter)) {
      for (int i = 0; i < list.size(); i++) {
        SideBase bean = list.get(i);
        if (bean.getLetterName().equals(letter)) {
          position = i;
          break;
        }
      }
    }
    return position;
  }
  
  public static String[] getLetters(List<? extends SideBase> list) {
    List<String> letters = new ArrayList<>();
    if (list != null && !list.isEmpty()) {
      for (int i = 0; i < list.size(); i++) {
        if (!letters.contains(list.get(i).getLetterName())) {
          letters.add(list.get(i).getLetterName());
        }
      }
    }
    return (String[]) letters.toArray(new String[letters.size()]);
  }
}

适配器CityAdapter:


package com.example.junweiliu.commindexdemo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.junweiliu.commindexdemo.R;
import com.example.junweiliu.commindexdemo.bean.CityBean;
import com.example.junweiliu.commindexdemo.util.CommUtil;
import java.util.List;

public class CityAdapter extends BaseAdapter {
  
  private Context context;
  
  private LayoutInflater mInflater;
  
  private List<CityBean> cityBeanList;
  
  public CityAdapter(Context context, List<CityBean> cityBeanList) {
    this.context = context;
    this.cityBeanList = cityBeanList;
  }
  @Override
  public int getCount() {
    return cityBeanList.size();
  }
  @Override
  public Object getItem(int i) {
    return cityBeanList.get(i);
  }
  @Override
  public long getItemId(int i) {
    return i;
  }
  @Override
  public View getView(int position, View convertView, ViewGroup viewGroup) {
    ViewHolder viewHolder = null;
    CityBean bean = cityBeanList.get(position);
    if (convertView == null) {
      convertView = mInflater.from(context).inflate(R.layout.item_city, null);
      viewHolder = new ViewHolder();
      viewHolder.headerTv = (TextView) convertView.findViewById(R.id.tv_item_citys_header);
      viewHolder.contentTv = (TextView) convertView.findViewById(R.id.tv_item_citys_context);
      convertView.setTag(viewHolder);
    } else {
      viewHolder = (ViewHolder) convertView.getTag();
    }
    // 如果当前位置为第一次出现该类首字母的位置,则显示headerTv
    if (position == CommUtil.getPositionForSection(position, cityBeanList)) {
      viewHolder.contentTv.setVisibility(View.VISIBLE);
      viewHolder.headerTv.setVisibility(View.VISIBLE);
      viewHolder.headerTv.setText(bean.getLetterName());
    } else {
      viewHolder.headerTv.setVisibility(View.GONE);
      viewHolder.contentTv.setVisibility(View.VISIBLE);
    }
    viewHolder.contentTv.setText(bean.getCityName());
    return convertView;
  }
  
  class ViewHolder {
    TextView headerTv;
    TextView contentTv;
  }
}

MainActivity:


package com.example.junweiliu.commindexdemo;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.TextView;
import com.example.junweiliu.commindexdemo.adapter.CityAdapter;
import com.example.junweiliu.commindexdemo.bean.CityBean;
import com.example.junweiliu.commindexdemo.util.CommUtil;
import com.example.junweiliu.commindexdemo.widget.SideBar;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
  
  private List<CityBean> cityBeanList = new ArrayList<>();
  
  private ListView cityList;
  
  private SideBar mSideBar;
  
  private TextView mShowTv;
  
  private CityAdapter mCityAdapter;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initData();
    initView();
  }
  
  private void initData() {
    CityBean bean1 = new CityBean("安徽", "A", "安徽故事");
    CityBean bean2 = new CityBean("安徽1", "A", "安徽1故事");
    CityBean bean3 = new CityBean("安徽2", "A", "安徽2故事");
    CityBean bean4 = new CityBean("北京", "B", "北京故事");
    CityBean bean5 = new CityBean("北京1", "B", "北京1故事");
    CityBean bean6 = new CityBean("北京2", "B", "北京2故事");
    CityBean bean7 = new CityBean("重庆", "C", "重庆故事");
    CityBean bean8 = new CityBean("重庆1", "C", "重庆1故事");
    CityBean bean9 = new CityBean("重庆2", "C", "重庆2故事");
    CityBean bean10 = new CityBean("贵州", "G", "贵州故事");
    CityBean bean11 = new CityBean("贵州1", "G", "贵州2故事");
    CityBean bean12 = new CityBean("贵州2", "G", "贵州3故事");
    CityBean bean13 = new CityBean("天津", "T", "天津故事");
    CityBean bean14 = new CityBean("天津1", "T", "天津1故事");
    CityBean bean15 = new CityBean("天津2", "T", "天津2故事");
    cityBeanList.add(bean1);
    cityBeanList.add(bean2);
    cityBeanList.add(bean3);
    cityBeanList.add(bean1);
    cityBeanList.add(bean2);
    cityBeanList.add(bean3);
    cityBeanList.add(bean4);
    cityBeanList.add(bean5);
    cityBeanList.add(bean6);
    cityBeanList.add(bean4);
    cityBeanList.add(bean5);
    cityBeanList.add(bean6);
    cityBeanList.add(bean7);
    cityBeanList.add(bean8);
    cityBeanList.add(bean9);
    cityBeanList.add(bean7);
    cityBeanList.add(bean8);
    cityBeanList.add(bean9);
    cityBeanList.add(bean10);
    cityBeanList.add(bean11);
    cityBeanList.add(bean12);
    cityBeanList.add(bean10);
    cityBeanList.add(bean11);
    cityBeanList.add(bean12);
    cityBeanList.add(bean13);
    cityBeanList.add(bean14);
    cityBeanList.add(bean15);
    cityBeanList.add(bean13);
    cityBeanList.add(bean14);
    cityBeanList.add(bean15);
  }
  
  private void initView() {
    cityList = (ListView) findViewById(R.id.lv_city);
    mSideBar = (SideBar) findViewById(R.id.sb_city);
    mShowTv = (TextView) findViewById(R.id.tv_city_show);
    mCityAdapter = new CityAdapter(MainActivity.this, cityBeanList);
    cityList.setAdapter(mCityAdapter);
    // 设置需要显示的索引栏内容
    mSideBar.setLetter(CommUtil.getLetters(cityBeanList));
    // 设置需要显示的提示框
    mSideBar.setTextView(mShowTv);
    mSideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {
      @Override
      public void onTouchingLetterChanged(String s) {
        int position = CommUtil.getLetterPosition(cityBeanList, s);
        if (position != -1) {
          cityList.setSelection(position);
        }
      }
    });
  }
}

布局文件activity_main:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.junweiliu.commindexdemo.MainActivity">
  <!--城市列表-->
  <ListView
      android:id="@+id/lv_city"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
  </ListView>
  <!--索引栏-->
  <com.example.junweiliu.commindexdemo.widget.SideBar
      android:id="@+id/sb_city"
      android:layout_width="wrap_content"
      android:layout_height="200dp"
      android:layout_alignParentRight="true"
      android:layout_centerVertical="true"
      app:sideTextSize="13sp"/>
  <!--显示的字母-->
  <TextView
      android:id="@+id/tv_city_show"
      android:layout_width="80dp"
      android:layout_height="80dp"
      android:layout_centerInParent="true"
      android:background="#3F51B5"
      android:gravity="center"
      android:textColor="#ffffff"
      android:textSize="25sp"
      android:visibility="gone"
  />
</RelativeLayout>

适配器布局item_city:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">
  <!-- 索引头 -->
  <TextView
      android:id="@+id/tv_item_citys_header"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#E3E3E3"
      android:padding="15dp"
      android:text="A"
      android:textColor="#666666"
      android:textSize="14sp"
      android:visibility="gone"/>
  <!-- 内容 -->
  <TextView
      android:id="@+id/tv_item_citys_context"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#ffffff"
      android:padding="15dp"
      android:textColor="#000000"
      android:textSize="14sp"
      android:visibility="gone"/>
</LinearLayout>
您可能感兴趣的文章:android仿微信通讯录搜索示例(匹配拼音,字母,索引位置)Android 实现带字母索引的侧边栏功能Android自定义View实现通讯录字母索引(仿微信通讯录)Android手机联系人带字母索引的快速查找Android手机联系人快速索引(手机通讯录)android将搜索引擎设置为中国雅虎无法搜索问题解决方法android 左右滑动+索引图标实现方法与代码Android ItemDecoration 实现分组索引列表的示例代码


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯