类 微信 通讯录,实时搜索,首字母分类,滚动header push,以及右侧字母列表

论坛 期权论坛 脚本     
匿名技术用户   2020-12-29 14:54   357   0

第一次写博客。。写的太渣请见谅。可以直接下代码~~

这段时间的项目中用到了这么一个类似的功能,主要用来选择城市。

刚好现在有空,所以提取整理出了代码。

当时在网上找了大概3-4个类似demo,但都功能不完整。所以决定参照那些demo自己写一个。


效果图



代码结构



先把思路说下。在每一个item上我都有一个head 和一个content,在adapter里判断当前item 的首字母是否首次出现,如果是首次出现,那么就显示head。
而那个可以被往上推动的headerView布局和 item 中 head的布局是一模一样的。在listView 滚动的时候,不断判断下一个首字母的position 是否是当前item首字母的position+1,如果是的话,那么就要往上推动了。不是的话悬浮在顶部不动。
那么是如何判断这两个position的呢,就要用到 AlphaChar这个对象了,里面有两个字段一个是首字母char ,一个是index 首字母的position。通过遍历获得下一个首字母的位置
Iterator<AlphaChar> iterator=alphaChars.iterator();
  while (iterator.hasNext()) {
   AlphaChar ac= iterator.next();
   if (ac.c==section &&iterator.hasNext()) {
    ac=iterator.next();
    return ac.index;
   }
  }
  return -1;

先把PinnedHeaderListView介绍一下。这个View 继承了ListView ,并且实现了OnScrollListener接口。
public class PinnedHeaderListView extends ListView implements OnScrollListener{
 private static final String TAG = "";

 public static final int PINNED_HEADER_GONE = 0;
 public static final int PINNED_HEADER_VISIBLE = 1;
 public static final int PINNED_HEADER_PUSHED_UP = 2;
 
 private static final int MAX_ALPHA = 255;

 private CityAdapter mAdapter;
 private View mHeaderView;
 private boolean mHeaderViewVisible;
 private int mHeaderViewWidth;
 private int mHeaderViewHeight;

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

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

 public PinnedHeaderListView(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
  if (isInEditMode()) {
   return;
  }
  
 }
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mHeaderView != null) {
            mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
            configureHeaderView(getFirstVisiblePosition());
        }
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHeaderView != null) {
            measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
            mHeaderViewWidth = mHeaderView.getMeasuredWidth();
            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        }
    }
 public void setPinnedHeaderView(View view) {
  mHeaderView = view;
  if (mHeaderView != null) {
   setFadingEdgeLength(0);
  }
  requestLayout();
  this.setOnScrollListener(this);
 }
    public void setAdapter(CityAdapter adapter) {
        super.setAdapter(adapter);
        
   mAdapter=adapter;
  
    }
    /**
     * 根据state状态 修改headView 的动作
     * @param position
     */
 public void configureHeaderView(int position) {
  if (mHeaderView == null) {
   return;
  }
  int state = getPinnedHeaderState(position);
  
  switch (state) {
  case PINNED_HEADER_GONE: {
   mHeaderViewVisible = false;
   break;
  }

  case PINNED_HEADER_VISIBLE: {
   configurePinnedHeader(position);
   if (mHeaderView.getTop() != 0) {
    mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
   }
   mHeaderViewVisible = true;
   break;
  }

  case PINNED_HEADER_PUSHED_UP: {
   View firstView = getChildAt(0);
   if(firstView != null){
    
    int bottom = firstView.getBottom();
    int headerHeight = mHeaderView.getHeight();
    int y;
    int alpha;
    if (bottom < headerHeight) {
     y = (bottom - headerHeight);
     alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
    } else {
     y = 0;
     alpha = MAX_ALPHA;
    }
    configurePinnedHeader(position);
    if (mHeaderView.getTop() != y) {
     
     mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight
       + y);
    }
    mHeaderViewVisible = true;
   }else{
    Log.w(TAG, "firstView ==null");
   }
   break;
  }
  }
 }
 /**
  * 判断当前是该隐藏还是悬浮 还是被推动上移
  * @param position
  * @return
  */
 public int getPinnedHeaderState(int position) {
  int realPosition = position;
  if (realPosition < 0) {
   return PINNED_HEADER_GONE;
  }

  int section = mAdapter.getSectionForPosition(realPosition);
  int nextSectionPosition = mAdapter.getNextPositionForSection(section);
  if (nextSectionPosition != -1
    && realPosition == nextSectionPosition - 1) {
   return PINNED_HEADER_PUSHED_UP;
  }
  return PINNED_HEADER_VISIBLE;
 }
 /**
  * 设置head的文字
  * @param position
  */
 private void configurePinnedHeader(int position){
  
  char title=(char) mAdapter.getSectionForPosition(position);
  String text=String.valueOf(title);

  ((TextView)mHeaderView.findViewById(R.id.query_ticket_header_text)).setText(text);
 }
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mHeaderViewVisible) {
            drawChild(canvas, mHeaderView, getDrawingTime());
        }
    }

 @Override
 public void onScrollStateChanged(AbsListView view, int scrollState) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void onScroll(AbsListView view, int firstVisibleItem,
   int visibleItemCount, int totalItemCount) {
  // TODO Auto-generated method stub
  configureHeaderView(firstVisibleItem);
 }
}
CityAdapter 实现了SectionIndexer接口。代码里注释还好,就不多说其他的了,直接贴代码
public class CityAdapter extends BaseAdapter implements SectionIndexer{

 private static final String TAG = "TicketCityAdapter";

 private List<TicketCity> list;
 private LayoutInflater inflater;

 private List<AlphaChar> alphaChars;
 
 public CityAdapter(Context context, List<TicketCity> list,List<AlphaChar> alphaChars) {
  // TODO Auto-generated constructor stub
  inflater = LayoutInflater.from(context);
  this.list = list;
  this.alphaChars=alphaChars;

 }

 public void updateListView(List<TicketCity> list,List<AlphaChar> alphaChars) {
  this.list = list;
  this.alphaChars=alphaChars;
  
  notifyDataSetChanged();
 }

 @Override
 public int getCount() {
  // TODO Auto-generated method stub
  return list.size();
 }

 @Override
 public TicketCity getItem(int position) {
  // TODO Auto-generated method stub
  return list.get(position);
 }

 @Override
 public long getItemId(int position) {
  // TODO Auto-generated method stub
  return position;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  // TODO Auto-generated method stub
  int section = getSectionForPosition(position);
  Holder holder = null;
  if (convertView == null) {
   convertView = inflater.inflate(R.layout.query_ticket_item, null);
   holder = new Holder();
   holder.mHeaderParent=(LinearLayout)convertView.findViewById(R.id.query_ticket_item_parent);
   holder.mHeaderText=(TextView)convertView.findViewById(R.id.query_ticket_item_header_text);
   holder.textView=(TextView)convertView.findViewById(R.id.query_ticket_item_text);
   convertView.setTag(holder);
  } else {
   holder = (Holder) convertView.getTag();
  }

  
  if (getPositionForSection(section) == position) {
   holder.mHeaderParent.setVisibility(View.VISIBLE);
   String text=list.get(position).cityShortPinyin.toUpperCase()
     .substring(0, 1);
   holder.mHeaderText.setText(text);
  } else {
   holder.mHeaderParent.setVisibility(View.GONE);
  }

  holder.textView.setText(list.get(position).cityName);
  return convertView;
 }

 /**
  * 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置 section=65 在总list中查开头是65的
  */
 @Override
 public int getPositionForSection(int section) {
  if (section < 0) {
   return -1;
  }
  for (AlphaChar ac : alphaChars) {
   if (ac.c==section) {
    return ac.index;
   }
  }
  return -1;
 }
 /**
  * 获得下一个首字母的位置
  * @param section
  * @return
  */
 public int getNextPositionForSection(int section) {
  if (section < 0 ) {
   return -1;
  }
  Iterator<AlphaChar> iterator=alphaChars.iterator();
  while (iterator.hasNext()) {
   AlphaChar ac= iterator.next();
   if (ac.c==section &&iterator.hasNext()) {
    ac=iterator.next();
    return ac.index;
   }
  }
  return -1;
 }
 /**
  * 根据ListView的当前位置获取分类的首字母的Char ascii值 
  * 例如cityShortPinyin=ags char= A return 65
  */
 @Override
 public int getSectionForPosition(int position) {
  // TODO Auto-generated method stub
  if (position >= list.size()) {
   return -1;
  }
  return list.get(position).cityShortPinyin.toUpperCase().charAt(0);
 }

 @Override
 public Object[] getSections() {
  // TODO Auto-generated method stub
  return null;
 }

 class Holder {
  
  LinearLayout mHeaderParent;
  
  TextView mHeaderText;
  
  TextView textView;

 }


}

然后是 MainActivity
public class MainActivity extends Activity {
 private static final String TAG = MainActivity.class.getSimpleName();

 private static final int HANDLER_MSG_SHOW = 1;
 private PinnedHeaderListView mListView;

 private CityAdapter mAdapter;

 private BladeView mLetter;

 private ClearEditText mClearEditText;

 private List<TicketCity> tcs;

 private List<AlphaChar> alphaChars;
 String[] datas;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mListView = (PinnedHeaderListView) findViewById(R.id.query_ticket_activity_list);
  mLetter = (BladeView) findViewById(R.id.query_ticket_activity_sidrbar);
  mClearEditText = (ClearEditText) findViewById(R.id.query_ticket_activity_edit);
  init();
 }

 private void init() {
  Thread thread = new Thread(new Runnable() {

   @Override
   public void run() {
    // TODO Auto-generated method stub
    // http获取数据等,这里直接读本地的文件了
    datas = getResources().getStringArray(R.array.countries);
    if (tcs==null) {
     tcs=new ArrayList<TicketCity>();
    }
    tcs.clear();
    TicketCity tc;
    for (int i = 0; i < datas.length; i++) {
     tc = new TicketCity();
     tc.cityShortPinyin = datas[i];
     tc.cityName = datas[i] + "名字" + i;
     tc.cityFullPinyin = datas[i] + "全拼" + i;
     tcs.add(tc);
    }
    Collections.sort(tcs);
    initSAlphaChar(tcs);
    mHandler.sendEmptyMessage(HANDLER_MSG_SHOW);
   }
  });
  thread.start();

 }

 private Handler mHandler = new Handler() {

  @Override
  public void handleMessage(Message msg) {
   // TODO Auto-generated method stub
   super.handleMessage(msg);
   switch (msg.what) {
   case HANDLER_MSG_SHOW:
    showView();
    break;

   default:
    break;
   }
  }

 };

 protected void showView() {
  // TODO Auto-generated method stub
  mLetter.setOnItemClickListener(new BladeView.OnItemClickListener() {

   @Override
   public void onItemClick(String s) {
    mListView.setSelection(mAdapter.getPositionForSection(s
      .toUpperCase().charAt(0)));
   }
  });

  mClearEditText.addTextChangedListener(new TextWatcher() {

   @Override
   public void onTextChanged(CharSequence s, int start, int before,
     int count) {
    try {
     filterData(s.toString());
    } catch (Exception e) {
    }
   }

   @Override
   public void beforeTextChanged(CharSequence s, int start, int count,
     int after) {
    // TODO Auto-generated method stub

   }

   @Override
   public void afterTextChanged(Editable s) {
    // TODO Auto-generated method stub

   }
  });

  mAdapter = new CityAdapter(this, tcs, alphaChars);

  mListView.setAdapter(mAdapter);
  mListView.setPinnedHeaderView(LayoutInflater.from(this).inflate(
    R.layout.query_ticket_header, mListView, false));
  mListView.setOnItemClickListener(new OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView<?> parent, View view,
     int position, long id) {
    // TODO Auto-generated method stub
    String py= mAdapter.getItem(position).cityShortPinyin;
    Toast.makeText(MainActivity.this, py, Toast.LENGTH_LONG).show();;
   }
   
  });
 }

 /**
  * 根据输入框中的值来过滤数据并更新ListView
  * 
  * @param filterStr
  */
 private void filterData(String filterStr) {

  List<TicketCity> filterDateList = new ArrayList<TicketCity>();

  if (TextUtils.isEmpty(filterStr)) {
   filterDateList = tcs;
  } else {
   filterDateList.clear();
   for (TicketCity tc : tcs) {
    // 简拼 全屏 汉字都匹配
    if (tc.cityShortPinyin.toUpperCase().indexOf(
      filterStr.toUpperCase()) >= 0
      || tc.cityName.indexOf(filterStr) >= 0
      || tc.cityFullPinyin.toUpperCase().indexOf(
        filterStr.toUpperCase()) >= 0) {
     filterDateList.add(tc);
    }

   }
  }

  // 根据a-z进行排序
  Collections.sort(filterDateList);
  initSAlphaChar(filterDateList);
  mAdapter.updateListView(filterDateList, alphaChars);

 }

 /**
  * 初始化起始城市首字母列表
  * 
  * @param list
  */
 private void initSAlphaChar(List<TicketCity> list) {

  if (alphaChars == null) {
   alphaChars = new ArrayList<AlphaChar>();
  }
  alphaChars.clear();

  char c;
  char oldc = 0;

  for (int i = 0; i < list.size(); i++) {

   c = list.get(i).cityShortPinyin.toUpperCase().charAt(0);

   if (c != oldc) {
    AlphaChar ac = new AlphaChar();
    ac.c = c;
    ac.index = i;
    alphaChars.add(ac);
   }
   oldc = c;
  }

 }

}

还有两个数据类就没啥好说的了
TicketCity 加了一个排序
public class TicketCity  implements Comparable<TicketCity>{
 /**
  * 城市名称
  */
 public String cityName;
 /**
  * 城市拼音全拼
  */
 public String cityFullPinyin;
 /**
  * 城市拼音简拼
  */
 public String cityShortPinyin;
 
 
 Comparator cmp = Collator.getInstance(java.util.Locale.CHINA);
 @Override
 public int compareTo(TicketCity another) {
  // TODO Auto-generated method stub
  
  
  return cmp.compare(this.cityShortPinyin, another.cityShortPinyin);
 }
 
}




xml的布局文件:
<?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:background="@android:color/white"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/query_ticket_item_parent"
    android:layout_width="fill_parent"
    android:layout_height="@dimen/dimen_query_ticket_listView_item_head_height"
    android:background="#e8e8e8"
    android:orientation="horizontal">

        <TextView
            android:id="@+id/query_ticket_item_header_text"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
            android:layout_marginLeft="@dimen/dimen_query_ticket_listView_item_text_margin_left"
            android:gravity="left|center_vertical"
            android:textColor="#696969"
            android:textSize="@dimen/dimen_query_ticket_listView_item_head_text_size" />

    </LinearLayout>

    <TextView
        android:id="@+id/query_ticket_item_text"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/dimen_query_ticket_listView_item_text_height"
        android:layout_marginLeft="@dimen/dimen_query_ticket_listView_item_text_margin_left"
        android:gravity="center_vertical"
        android:text="城市"
        android:textSize="@dimen/dimen_query_ticket_listView_item_text_size" />

</LinearLayout>
主界面布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="activity.MainActivity" >

    <RelativeLayout
        android:id="@+id/query_ticket_activity_title_bar"
        android:layout_width="match_parent"
        android:layout_height="46dp"
        android:layout_alignParentRight="true"
        android:background="#47abef" >

        <view.ClearEditText
            android:id="@+id/query_ticket_activity_edit"
            android:layout_width="@dimen/dimen_query_ticket_edittext_width"
            android:layout_height="@dimen/dimen_query_ticket_edittext_height"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/query_ticket_activity_back"
            android:background="@drawable/ico_search_box"
            android:drawableLeft="@drawable/ico_search"
            android:drawablePadding="14dp"
            android:hint="城市、车站的中文或拼音"
            android:paddingLeft="@dimen/dimen_query_ticket_edittext_search_pading_left"
            android:singleLine="true"
            android:textColor="#c2c2c2"
            android:textColorHint="#c2c2c2"
            android:textSize="@dimen/dimen_query_ticket_edittext_text_size" />
    </RelativeLayout>

    <view.PinnedHeaderListView
        android:id="@+id/query_ticket_activity_list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/query_ticket_activity_title_bar"
        android:layout_gravity="center"
        android:cacheColorHint="#00000000"
        android:divider="#e8e8e8"
        android:dividerHeight="1px"
        android:fadingEdge="none" />

    <view.BladeView
        android:id="@+id/query_ticket_activity_sidrbar"
        android:layout_width="30.0dip"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/query_ticket_activity_title_bar"
        android:layout_gravity="right" />

</RelativeLayout>




资源下载链接点击打开链接




分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP