您的位置:首页 > 移动开发 > Android开发

Android自定义A_Z字母排序ListView,悬停Listview

2017-12-25 18:11 459 查看
近期项目有个功能,要开发一个通讯录,什么样的通讯录呢:

1.带字母分组的悬停

2.右边带字母索引的侧边栏

3.与顶部搜索框进行动态变化

4.点击Item可以直接拨电话、复制、发邮件

想想一个通讯录要求咋这么多呢,不过码农还是得干啊,先来一张成品图





先来分析一波UI设计的剧本:

1.顶部一个搜索框,点击后,当listview滑动且输入框无内容时,它的提示图片和文字要显示出来

2.侧边字母表要与listview相呼应,点击某个字母,这个字母要放大加圈显示,且listivew要定位到对应的分组

3.listview在输入框下方,需要做分组悬停,且与侧边字母表对应

1.那就先做吧。先从顶部EditText做起,定义一个类EditTextWithImg

public class EditTextWithImg extends AppCompatEditText {

private Bitmap searchBitmap;

private float txtSize;
private int  txtColor;
private String txt;

private int height;//控件本身的高度
private int width;//控件本身的宽度
private float searchLeft;//搜索图片 距离控件左边的距离
private float searchTop ;//搜索图片距离控件顶部的距离

private Paint txtPaint;

private boolean isInput;//是否是手动开始输入
private boolean isNeedDraw ;//避免重复绘制

private InputListener inputListener;

public void setInputListener(InputListener inputListener) {
this.inputListener = inputListener;
}

/**
* 当listview进行滑动时,要判断下输入框有无内容,没有就恢复初始状态
*/
public void setInput() {
if (TextUtils.isEmpty(getText().toString().trim()) && isNeedDraw) {
setInputMode(false);
if (inputListener != null) {
inputListener.inputOver();
}
}
}

/**
* 设置edittext是否获取焦点
* @param isInput
*/
public void setInputMode(Boolean isInput){
this.isInput = isInput;
isNeedDraw = isInput;
setFocusable(isInput);
setFocusableInTouchMode(isInput);
invalidate();
}

public EditTextWithImg(Context context, AttributeSet attrs) {
super(context, attrs);
initResource(attrs);
initPaint();
}

public EditTextWithImg(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initResource(attrs);
initPaint();
}

private void initResource(AttributeSet attrs) {
//自定义的一些属性 提示文字大小,颜色,内容
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.etimg);
float density = getResources().getDisplayMetrics().density;//像素密度
txtSize = typedArray.getDimension(R.styleable.etimg_edit_txtsize,11*density+0.5f);
txtColor = typedArray.getColor(R.styleable.etimg_edit_txtcolor,0x000000);
txt = typedArray.getString(R.styleable.etimg_edit_txt);
typedArray.recycle();
}

private void initPaint() {
txtPaint = new Paint();
txtPaint.setTextSize(txtSize);
txtPaint.setColor(txtColor);
txtPaint.setAntiAlias(true);
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (searchBitmap == null)
searchBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.img_search);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
height = getHeight();
width = getWidth();

searchLeft = width / 2 - searchBitmap.getWidth() - dpToPx(1);
searchTop = height / 2 - searchBitmap.getHeight() / 2 + dpToPx(1);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//当没有点击输入框且输入框无内容时,绘制初始状态
if (!isInput && TextUtils.isEmpty(getText().toString().trim())) {
canvas.drawText(txt, width / 2, height / 2 + txtSize / 2, txtPaint);
if (searchBitmap != null) {
canvas.drawBitmap(searchBitmap, searchLeft, searchTop, null);
}
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (searchBitmap != null && !searchBitmap.isRecycled())
searchBitmap.recycle();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//当手开始点击时,获取焦点,清空初始状态
setInputMode(true);
if (inputListener != null) {
inputListener.inputBegin();
}
break;
}
return super.onTouchEvent(event);
}

private int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}

public interface InputListener{
void inputBegin();
void inputOver();
}
}


具体都写了注释,大概流程是

1.先绘制初始时的状态,即显示提示语(搜索)和搜索图标;

2.当手开始点击进行输入时,让控件获取焦点,去除提示语和搜索图标

3.当Listview进行滑动时,判断下控件里有没有内容,没有的话就恢复初始状态

2.再来定义一个类绘制侧边字母表SideLetterBar

public class SideLetterBar extends View {

private static final String[] index = {"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 static LinkedHashMap<String,Integer> indexMap = new LinkedHashMap<>();
static {
indexMap.put("A",0);indexMap.put("B",1);
indexMap.put("C",2);indexMap.put("D",3);
indexMap.put("E",4);indexMap.put("F",5);
indexMap.put("G",6);indexMap.put("H",7);
indexMap.put("I",8);indexMap.put("J",9);
indexMap.put("K",10);indexMap.put("L",11);
indexMap.put("M",12);indexMap.put("N",13);
indexMap.put("O",14);indexMap.put("P",15);
indexMap.put("Q",16);indexMap.put("R",17);
indexMap.put("S",18);indexMap.put("T",19);
indexMap.put("U",20);indexMap.put("V",21);
indexMap.put("W",22);indexMap.put("X",23);
indexMap.put("Y",24);indexMap.put("Z",25);
indexMap.put("#",26);
}

private int choose = -1;//当前选中的字母的位置

private Paint paint = new Paint();
private Paint paintCircle = new Paint();

private OnLetterChangedListener onLetterChangedListener;

private TextView overlay;

private float circleradius ;
private int circleColor ;
private float textSize;
private float textSize_Big;
private int textColor;
private int textChooseColor;

private int height;//控件高度
private int width;//控件宽度
private int singleHeight;//每个字母所占的高度

public SideLetterBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public SideLetterBar(Context context, AttributeSet attrs) {
super(context, attrs);
}

public SideLetterBar(Context context) {
super(context);
}

/**
* 设置悬浮的textview
* @param overlay
*/
public void setOverlay(TextView overlay){
this.overlay = overlay;
}

/**
* listview拖动时候 顶部第一个item定位到对应的字母
* @param letter
*/
public void drawA_ZCircle(String letter){
if (TextUtils.isEmpty(letter)) return;
choose = indexMap.get(letter);
invalidate();
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);

height = getHeight();
width = getWidth();
singleHeight = height / index.length;

circleradius = getResources().getDimension(R.dimen.side_letter_bar_clrcle_size);
circleColor = getResources().getColor(R.color.letter_clrclr);

textSize = getResources().getDimension(R.dimen.side_letter_bar_letter_size);
textSize_Big = getResources().getDimension(R.dimen.side_letter_bar_letter_bigsize);
textColor = getResources().getColor(R.color.letter_text);
textChooseColor = getResources().getColor(R.color.white);
}

@SuppressWarnings("deprecation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

for (int i = 0; i < index.length; i++) {
paint.setAntiAlias(true);
if (i == choose) {
//对选中字母进行单独绘制
paint.setColor(textChooseColor);
paint.setTextSize(textSize_Big);
paintCircle.setColor(circleColor);
paintCircle.setAntiAlias(true);
paintCircle.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, singleHeight * (choose + 1) - singleHeight / 3,
circleradius, paintCircle);
} else {
paint.setTextSize(textSize);
paint.setColor(textColor);
}
float xPos = width / 2 - paint.measureText(index[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(index[i], xPos, yPos, paint);
paint.reset();
}

}

/**
* 因为在Listview上方,所以当点击事件来的时候,直接return ,不进行向下分发
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int c = (int) (y / singleHeight);
final int oldChoose = choose;

switch (action) {
case MotionEvent.ACTION_DOWN:
if (oldChoose != c && onLetterChangedListener != null) {
if (c >= 0 && c < index.length) {
changeLetterBar(c);
}
}

break;
case MotionEvent.ACTION_MOVE:
if (oldChoose != c && onLetterChangedListener != null) {
if (c >= 0 && c < index.length) {
changeLetterBar(c);
}
}
break;
case MotionEvent.ACTION_UP:
choose = c;
invalidate();
if (overlay != null){
overlay.setVisibility(GONE);
}
break;
}
return true;
}

private void changeLetterBar(int letterPosition) {
onLetterChangedListener.onLetterChanged(index[letterPosition]);
choose = letterPosition;
invalidate();
//设置悬浮view
if (overlay != null){
overlay.setVisibility(VISIBLE);
overlay.setText(index[letterPosition]);
}
}

public void setOnLetterChangedListener(OnLetterChangedListener onLetterChangedListener) {
this.onLetterChangedListener = onLetterChangedListener;
}

public interface OnLetterChangedListener {
void onLetterChanged(String letter);
}

}


主要流程就是

1.初始没有字母被选中,就在onDraw里把每个字母绘制在侧边栏

2.当手点击某一个字母时,因为字母表在相对布局中是处于listview立体空间中的上方,所以点击事件在dispatchTouchEvent里进行处理,主要是return true不让事件进行分发,然后确定好选中字母的位置,重新在onDraw里进行绘制

3.当listview进行拖动时候,会通过drawA_ZCircle方法通知该view,在indexMap里找到listview的顶部第一个item的首字母的位置,然后再重新绘制

3.接下里就是重写Listview了

public class SortListView extends ListView implements AbsListView.OnScrollListener {

private final String TAG = "SortListView";

private TextView tv_hoverTitle;//顶部悬停view
private EditTextWithImg editText;//顶部搜索view
private SideLetterBar side_letterbar;//侧边字母表

private String lastPy;

private boolean isScroll;//是否滑动listview
private boolean isChackLetterBar;//是否手动选中侧边字母表

public SortListView(Context context) {
super(context);
initListener();
}

public SortListView(Context context, AttributeSet attrs) {
super(context, attrs);
initListener();
}

public SortListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initListener();
}

private void initListener(){
setOnScrollListener(this);
}

public void setTv_hoverTitle(TextView tv_hoverTitle) {
this.tv_hoverTitle = tv_hoverTitle;
}

public void setEditText(EditTextWithImg editText) {
this.editText = editText;
}

public void setSide_letterbar(final SideLetterBar side_letterbar) {
this.side_letterbar = side_letterbar;
side_letterbar.setOnLetterChangedListener(new SideLetterBar.OnLetterChangedListener() {
@Override
public void onLetterChanged(String letter) {
isChackLetterBar = true;
lastPy = letter;
//设置顶部悬停view
tv_hoverTitle.setVisibility(View.VISIBLE);
tv_hoverTitle.setText(letter);
//设置listview位置
PeopleAdapter peopleAdapter = (PeopleAdapter) getAdapter();
int position = peopleAdapter.getLetterPosition(letter);
setSelection(position);
//然后绘制侧边字母表选中状态
side_letterbar.drawA_ZCircle(letter);
}
});
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN :
//当手开始触摸屏幕时候才进行联动 避免刚初始化就绘制报错
isScroll = true;
break;
case MotionEvent.ACTION_MOVE :
//当手开始滑动屏幕时解除手动选中字母表行为
isChackLetterBar = false;
break;
}
return super.onTouchEvent(ev);
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//当滑动时,如果edittext没有输入内容 就清空焦点,恢复初始状态
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
&& editText != null && TextUtils.isEmpty(editText.getText().toString().trim())) {
editText.setInput();
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

if (!isScroll) return;
if (isChackLetterBar) return;
if (side_letterbar == null) return;
if (tv_hoverTitle == null) return;
if (getChildAt(0) == null) return;

People p = (People) getAdapter().getItem(firstVisibleItem);
if (p == null) return;
String py = p.getPinyin();

int top = Math.abs(getChildAt(0).getTop());//选取当前屏幕可见的第一个item距离顶部距离
if (top == 0) {//一开始就设置显示,防止闪烁情况
tv_hoverTitle.setText(py.toUpperCase());
tv_hoverTitle.setVisibility(View.VISIBLE);
} else {
//页面第一次进来后再次滑动,后面的itemview gettop值总不为0
if (!py.equals(lastPy)) {
tv_hoverTitle.setText(py.toUpperCase());
tv_hoverTitle.setVisibility(View.VISIBLE);
}
}

//滑动时判断当前屏幕第一个item是什么字母开头的,然后绘制侧边字母表选中状态
if (!py.equals(lastPy) ) {//避免重复绘制
if (TextUtils.equals("#", py)) {
side_letterbar.drawA_ZCircle(py);
} else {
side_letterbar.drawA_ZCircle(py.toUpperCase());
}
}

lastPy = py;

}
}


listview是需要跟其它两个view进行互动的,主要流程就是

1.在onScrollStateChanged里进行判断,如果开始滑动了,就通知下Edittext状态是否需要变化

2.在onScroll里获取第一个可见的item的对象和view,判断侧边字母表需要标注哪个字母,listview顶部悬停分组该是哪个字母

3.在侧边字母表的的拖动监听里,设置相应的悬停字母,listview的position

这三个写完就结束了,如何进行使用可以到我的Github下载Demo

没有梯子的可以在这个地址下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐