Android通用索引栏实现代码

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

/**
 * Created by junweiliu on 16/11/24.
 */
public class SideBar extends View {
  /**
   * 点击回调
   */
  private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
  /**
   * 26字母
   */
  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();
  /**
   * 显示的TextView
   */
  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();
  }

  /**
   * 为SideBar设置显示字母的TextView
   *
   * @param mTextDialog
   */
  public void setTextView(TextView mTextDialog) {
    this.mTextDialog = mTextDialog;
  }

  /**
   * 设置
   *
   * @param letter
   */
  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);
  }

  /**
   * 绘制
   *
   * @param canvas
   */
  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;
  }

  /**
   * 向外公开的方法
   *
   * @param onTouchingLetterChangedListener
   */
  public void setOnTouchingLetterChangedListener(
      OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
    this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
  }

  /**
   * 回调接口
   *
   * @author coder
   */
  public interface OnTouchingLetterChangedListener {
    void onTouchingLetterChanged(String s);
  }

  /**
   * 测量
   *
   * @param widthMeasureSpec
   * @param heightMeasureSpec
   */
  @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;

/**
 * Created by junweiliu on 16/11/21.
 */
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;
  }

  /**
   * 获取索引
   *
   * @return
   */
  @Override
  public String getLetterName() {
    return cityHeader;
  }
}

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

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

  /**
   * 根据当前选中的项获取其第一次出现该项首字母的位置
   *
   * @param position 当前选中的位置
   * @param datas  数据源
   * @return
   */
  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()方法即可.还可以做其他处理:

  /**
   * 获取所选中的索引在列表中的位置
   *
   * @param list
   * @param letter
   * @return
   */
  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;
  }

  /**
   * 筛选出数据源中所包含的全部索引值
   *
   * @param list
   * @return
   */
  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;

/**
 * Created by junweiliu on 16/11/24.
 */
public class CommUtil {

  /**
   * 根据当前选中的项获取其第一次出现该项首字母的位置
   *
   * @param position 当前选中的位置
   * @param datas  数据源
   * @return
   */
  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;
  }

  /**
   * 获取所选中的索引在列表中的位置
   *
   * @param list
   * @param letter
   * @return
   */
  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;
  }

  /**
   * 筛选出数据源中所包含的全部索引值
   *
   * @param list
   * @return
   */
  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;

/**
 * Created by junweiliu on 16/11/24.
 */
public class CityAdapter extends BaseAdapter {
  /**
   * 上下文
   */
  private Context context;
  /**
   * 布局加载器
   */
  private LayoutInflater mInflater;
  /**
   * 数据源
   */
  private List<CityBean> cityBeanList;

  /**
   * 构造方法
   *
   * @param context
   * @param 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;
  }

  /**
   * vh
   */
  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<>();
  /**
   * 城市lv
   */
  private ListView cityList;
  /**
   * 索引栏
   */
  private SideBar mSideBar;
  /**
   * 显示的tv
   */
  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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • android仿微信通讯录搜索示例(匹配拼音,字母,索引位置)

    前言: 仿微信通讯录搜索功能,通过汉字或拼音首字母找到匹配的联系人并显示匹配的位置 一:先看效果图 字母索引 搜索匹配 二:功能分析 1:汉字转拼音 通讯录汉字转拼音(首个字符当考虑姓氏多音字), 现在转换拼音常见的有pinyin4j和tinypinyin, pinyin4j的功能强大,包含声调多音字,tinypinyin执行快占用内存少, 如果只是简单匹配通讯录,建议使用tinypinyin,用法也很简单这里不详细介绍 拼音类 public class CNPinyin <T extends

  • android 左右滑动+索引图标实现方法与代码

    使用Gallery和ImageView实现android左右滑动+索引图标效果. 首先自定义Gallery实现一次只能滑动一个页面 复制代码 代码如下: public class MGalleryView extends Gallery{ public MGalleryView(Context context, AttributeSet attrs) { super(context, attrs); } //一次只能滑动一张图片注:一张图充满全屏 @Override public boolean

  • Android自定义View实现通讯录字母索引(仿微信通讯录)

    一.效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等.这里我截了一张美团选择城市的图片来看看: 我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母.这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力! 二.分析: 我们看到这样的效果我们心理都回去琢磨,他是如何实现的: 首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件

  • Android ItemDecoration 实现分组索引列表的示例代码

    本文介绍了Android ItemDecoration 实现分组索引列表的示例代码,分享给大家.具体如下: 先来看看效果: 我们要实现的效果主要涉及三个部分: 分组 GroupHeader 分割线 SideBar 前两个部分涉及到一个ItemDecoration类,也是我们接下来的重点,该类是RecyclerView的一个抽象静态内部类,主要作用就是给RecyclerView的ItemView绘制额外的装饰效果,例如给RecyclerView添加分割线. 使用ItemDecoration时需要继

  • android将搜索引擎设置为中国雅虎无法搜索问题解决方法

    该问题是由于yahoo的搜索接口改变导致,请修改 Donottranslate-all_search_engines.xml (x:\6575gb2\v2.12\alps\mediatek\source\frameworks\banyan\res\res\values)41753 8/11/2011 中的<string-array name="yahoo_cn" translatable="false">的定义为 复制代码 代码如下: <strin

  • Android手机联系人快速索引(手机通讯录)

    最近需要实现一个手机通讯录的快速索引功能.根据姓名首字母快速索引功能.下面是一个手机联系人快速索引的效果,总体来说代码不算难,拼音转换的地方略有复杂.下面上源码:源码中有注释. 下面是效果图: MainActivity: import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.Activity; import android.os.Bundle; imp

  • Android手机联系人带字母索引的快速查找

    喜欢另辟蹊径的我,在这里废话不多说了,直接上代码和图片了. 效果图如下: 第一步:MainActivity的代码如下: package net.loonggg.test; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.TreeSet; import android.os.Bundle; import and

  • Android 实现带字母索引的侧边栏功能

    之前已经用自定义View做出如下这样一个效果了 这两天需要重新拿来使用,发现效果虽然做出来了,不过思路不太对,就重新参考写了一个,用法也更为简单了 首要的自然是需要继承View绘制出侧边栏,并向外提供一个监听字母索引变化的方法 /** * 作者:叶应是叶 * 时间:2017/8/20 11:38 * 描述: */ public class LetterIndexView extends View { public interface OnTouchingLetterChangedListener

  • Android通用索引栏实现代码

    偶尔看到之前写过的代码,感觉好多东西几乎在很多项目中都要用到,虽然每个项目的需求和设计都不同,不过实现的效果都是一样的,可能只是数据格式和一些颜色等的细微差距.但是有的时候因为一个小改变,就要去重复的修改代码,麻烦不说,也容易导致新的问题和BUG. 就拿忽然想到的索引栏来说,几乎写过的项目中都用到了,比如城市选择.联系人等等.这些地方全都需要用到索引栏,但是用法都是一样的.翻看了几处之前写过的代码,发现每次用到索引栏,都要重新去写方法来处理数据或者对数据的索引进行提取这些,做法也都大同小异.于是

  • Android实现通用筛选栏

    今天来写一个通用的筛选栏的实现,也是因为之前项目中要好多地方用到筛选栏这么个东西,所以为了之后用起来比较方便,就简单的做了一些封装.废话不多说,看一下效果图: 很多APP都有用到这个筛选栏,相信大家也不陌生. 一.需求分析 看到这个筛选栏之后,先来考虑一下布局,在开始接触android时候的我,可能看到这个布局的想法是:筛选栏这个部分,一个LinearLayout的横向布局,然后再分为3个部分,每个部分各占比重为1,然后在每个部分中放一个TextView和ImageView,之后监听每个部分的点

  • Android程序开发之Fragment实现底部导航栏实例代码

    流行的应用的导航一般分为两种,一种是底部导航,一种是侧边栏. 说明 IDE:AS,Android studio; 模拟器:genymotion; 实现的效果,见下图. 具体实现 为了讲明白这个实现过程,我们贴出来的代码多一写,这样更方便理解 [最后还会放出完整的代码实现] .看上图的界面做的比较粗糙,但实现过程的骨架都具有了,想要更完美的设计,之后自行完善吧 ^0^. 布局 通过观察上述效果图,发现任意一个选项页面都有三部分组成: 顶部去除ActionBar后的标题栏: 中间一个Fragment

  • Android侧滑导航栏的实例代码

    今天学习的新内容是侧滑导航栏,我想大家肯定都比较熟悉了,因为这个效果在qq里面也有,最近一直跟室友们玩的游戏是快速让自己的头像的点赞量上千.当然我的效果跟qq是没有办法比的,因为那里面的功能是在是太强大了.下面我来展示一下我做的效果截图. 我做的界面有点丑,但是对比之前已经是有了很大的改观了.想做这样的效果的话可以ps几张比较好看的图片. 下面就是粘贴我代码的时间了. activity_main.xml <cn.edu.rjxy.activity.DragLayout xmlns:android

  • Android实现listview滑动时渐隐渐现顶部栏实例代码

    我在开发的时候遇到了这样的需求,就是在listview的滑动中,需要对顶部的栏目由透明慢慢的变为不透明的状态,就是以下的效果 最先开始的时候想的很简单,无非就是监听listview的滑动距离,然后根据距离算出透明度的比值,就可以了,但是事实上呢也的确是这样做的 只是在获取listview的滑动距离上可能没法直接获取,需要动态的去计算 下面贴出全部代码吧,不想码字了,最近感冒了,脑袋晕乎乎的,还疼,代码更直观一些 private void initListener() { lvList.setOn

  • Android实现沉浸式导航栏实例代码

    废话不多说了,直接给大家贴代码了,具体代码如下所示: private SystemBarTintManager tintManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // getWindow().addFlags(WindowManager.Layo

  • Android自定义View实现字母导航栏的代码

    思路分析: 1.自定义View实现字母导航栏 2.ListView实现联系人列表 3.字母导航栏滑动事件处理 4.字母导航栏与中间字母的联动 5.字母导航栏与ListView的联动 效果图: 首先,我们先甩出主布局文件,方便后面代码的说明 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/re

  • 超简单的几行代码搞定Android底部导航栏功能

    超简单,几行代码搞定Android底部导航栏-–应项目需求以及小伙伴的留言,新加了两个方法: 设置底部导航栏背景图片 添加底部导航栏选项卡切换监听事件 底部导航栏的实现也不难,就是下边是几个Tab切换,上边一般是一个FrameLayout,然后FrameLayout中切换fragment. 网上有不少关于Android底部导航栏的文章,不过好像都只是关于下边Tab切的,没有实现Tab与fragment的联动,用的时候还要自己手写这部分代码,对我这个比较懒(据说,懒是程序员的一种美德_#)得程序员

  • Android开心消消乐代码实例详解

    突然想要在android上写一个消消乐的代码,在此之前没有系统地学过java的面向对象,也没有任何android相关知识,不过还是会一点C++.8月初开始搭建环境,在这上面花了相当多的时间,然后看了一些视频和电子书,对android有了一个大概的了解,感觉差不多了的时候就开始写了. 疯狂地查阅各种资料,反反复复了好几天后,也算是写出了个成品.原计划有很多地方还是可以继续写下去的,比如UI设计,比如动画特效,时间设计,关卡设计,以及与数据库的连接,如果可以的话还能写个联网功能,当然因为写到后期内心

  • Android底部导航栏的动态替换方案

    Android底部导航栏的动态替换方案,供大家参考,具体内容如下 1.通常来说,一般情况下,我们的app的BottomTab会有下面几种实现方式. 1).自定义view,然后自己写逻辑去实现互斥. 2).使用RadioGroup+RadioButton去实现底部的Tab. 自由度比极高,如果想实现搞复杂度的话可以重写 RadioButton. 3).使用google design包里面的 TabLayout去实现. 可上.可下.可以滑动 偷懒的话可以根据已有api来设置一些资源,也可以 setC

随机推荐