Android 自定义View实现城市选择列表

使用自定义View的方法,实现一个城市选择列表
这里写图片描述

手指滑动时
这里写图片描述

自定义View实现侧边栏,并提供回调接口


/**
 * Created by shixi_tianrui1 on 16-9-18.
 * 城市选择列表侧边栏
 */
public class LetterSideBar extends View {

    // 字母列表
    private List<String> mLetters = new ArrayList<>();
    private OnTouchLetterListener mOnTouchLetterListener;
    private TextView mOverLayTextView; // 显示的字母

    private int mWidth;
    private int mHeight;
    private int mEachLetterHeight;
    private int mEachLetterWidth;

    private Paint mPaint;

    private boolean maskShown; // 是否显示背景
    private int mSelectedIndex = -1; // 已选择的字母

    public LetterSideBar(Context context) {
        this(context, null);
    }

    public LetterSideBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LetterSideBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();

        // for test
        for (int i = 0; i < 26; i++) {
            char letter = (char) ('A' + i);
            Log.v("LOG", letter + "");
            mLetters.add(String.valueOf(letter));
        }
    }


    /**
     * 测量并绘制所有的大写字母
     *
     * @param canvas 系统画布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mWidth = getWidth();
        mHeight = getHeight();
        mPaint.setAntiAlias(true); // 抗锯齿
        mPaint.setTextSize(getResources().getDimension(R.dimen.side_bar_letter));
        if (mLetters.size() > 0) {
            mEachLetterHeight = mHeight / mLetters.size();
        } else {
            mEachLetterHeight = mHeight;
        }
        // 绘制所有显示的字母
        mEachLetterWidth = (int) (mWidth / 2 - mPaint.measureText(mLetters.get(0)) / 2);
//        Log.v("LOG", "eachLetterWidth:" + mEachLetterWidth);
        for (int i = 0; i < mLetters.size(); i++) {
            mPaint.setColor(Color.RED);
            if (mSelectedIndex == i && mSelectedIndex != -1) {
                mPaint.setColor(Color.CYAN); // 设置选中时的颜色
            }
            canvas.drawText(mLetters.get(i), mEachLetterWidth, i * mEachLetterHeight + mEachLetterHeight, mPaint);
        }
    }


    /**
     * 处理触摸事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float y = event.getY();
        // 获取点击的字母
        int letterIndex = (int) ((y / mHeight) * mLetters.size());
        switch (action) {
            // 选中字母高亮
            case MotionEvent.ACTION_DOWN:
                L.d("Letter is : " + mLetters.get(letterIndex));
                selectedLetter(letterIndex);
                break;
            // 在字母范围内,仍视为选中字母
            case MotionEvent.ACTION_MOVE:
                selectedLetter(letterIndex);
                break;
            case MotionEvent.ACTION_UP:
                mSelectedIndex = -1;
                invalidate();
                hideOverLayText();
                break;
        }
        return true;
    }

    public void setLetterList(List<String> list) {
        this.mLetters = list;
    }


    public void setOnTouchLetterListener(OnTouchLetterListener onTouchLetterListener) {
        this.mOnTouchLetterListener = onTouchLetterListener;
    }


    /**
     * 通过此接口设置ListView的position
     */
    public interface OnTouchLetterListener {
        void onLetterSelected(String letter);
    }


    /**
     * 高亮显示选中的字母,并显示mask
     */
    private void selectedLetter(int letterIndex) {
        if (letterIndex >= 0 && letterIndex < mLetters.size()) {
            // 显示遮罩
            maskShown = true;
            if (mOnTouchLetterListener != null) {
                mOnTouchLetterListener.onLetterSelected(mLetters.get(letterIndex));
            }
            mSelectedIndex = letterIndex;
            // 通知重绘
            invalidate();
            // 显示字母
            showOverLayText();
        }
    }

    /**
     * 显示选择的字母
     */
    public void showOverLayText() {
        if (mOverLayTextView != null) {
            mOverLayTextView.setVisibility(View.VISIBLE);
            mOverLayTextView.setText(mLetters.get(mSelectedIndex));
        }
    }

    public void hideOverLayText() {
        if (mOverLayTextView != null) {
            mOverLayTextView.setVisibility(View.GONE);
        }
    }

    public TextView getOverLayTextView() {
        return mOverLayTextView;
    }

    public void setOverLayTextView(TextView overLayTextView) {
        this.mOverLayTextView = overLayTextView;
    }


}

CityAdapter 给ListView添加城市数据


/**
 * Created by shixi_tianrui1 on 16-9-27.
 */
public class CityAdapter extends BaseAdapter {

    // ListView Type
    private static final int TYPE_CITY_ITEM = 1;
    private static final int TYPE_SEARCH_VIEW = 0;
    private static final int TYPE_VIEW_COUNT = 2; // 所有的View类型

    private Context mContext;
    private List<CityBean> mCities;
    private HashMap<CityBean, Integer> mLetterPos = new LinkedHashMap<>();

    public CityAdapter(Context context, List<CityBean> cities) {
        mContext = context;
        mCities = cities;
        mCities.add(0, new CityBean()); // 添加一个空的CityBean
        // record city's pinyin bound
        mLetterPos.put(cities.get(0), 1);
        for (int i = 1; i < mCities.size(); i++) {
            CityBean prev = mCities.get(i - 1);
            CityBean cur = mCities.get(i);
            if (!TextUtils.equals(Util.getFirstLetter(prev.getPinyin())
                    , Util.getFirstLetter(cur.getPinyin()))) {
                mLetterPos.put(cur, i);
            }
        }
        L.d(mLetterPos.toString());
    }

    @Override
    public int getCount() {
        return mCities.size();
    }

    @Override
    public Object getItem(int i) {
        return mCities.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View convertView, ViewGroup viewGroup) {
        int viewType = getItemViewType(i);
        switch (viewType) {
            case TYPE_SEARCH_VIEW: // 搜索框
                convertView = LayoutInflater.from(mContext).inflate(R.layout.item_search, viewGroup, false);
                break;
            case TYPE_CITY_ITEM: // 城市Item
                ViewHolder holder;
                if (convertView == null || convertView.getTag() == null) {
                    holder = new ViewHolder();
                    LayoutInflater inflater = LayoutInflater.from(mContext);
                    convertView = inflater.inflate(R.layout.item_city, viewGroup, false);
                    holder.mTvCity = (TextView) convertView.findViewById(R.id.id_tv_city_name);
                    holder.mTvLetter = (TextView) convertView.findViewById(R.id.id_tv_letter);
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }
                CityBean curCity = mCities.get(i);
                if (mLetterPos.containsKey(curCity)) {
                    holder.mTvLetter.setVisibility(View.VISIBLE);
                    String letter = Util.getFirstLetter(curCity.getPinyin());
                    if (!TextUtils.isEmpty(letter)) {
                        holder.mTvLetter.setText(letter.toUpperCase());
                    }
                } else {
                    holder.mTvLetter.setVisibility(View.GONE);
                }
                holder.mTvCity.setText(curCity.getName());
                break;
        }
        return convertView;
    }

    @Override
    public int getItemViewType(int position) {
//        L.d("getItemViewType : " + position);
        return position >= TYPE_VIEW_COUNT - 1 ? TYPE_CITY_ITEM : TYPE_SEARCH_VIEW;
    }


    private class ViewHolder {
        private TextView mTvLetter;
        private TextView mTvCity;
    }

    public int getPosition(String letter) {

        int position = -1;
        Set<CityBean> set = mLetterPos.keySet();
        Iterator<CityBean> it = set.iterator();
        CityBean city = null;
        while (it.hasNext()) {
            city = it.next();
            if (TextUtils.equals(city.getFirstLetter(), letter.toLowerCase())) {
                return mLetterPos.get(city);
            }
        }
        return -1;
    }
}

Controller层处理回调


/**
 * Created by shixi_tianrui1 on 16-9-25.
 */
public class CityPickController implements LetterSideBar.OnTouchLetterListener {

    private View mRootView;
    private TextView mTvMask;
    private LetterSideBar mLsSidebar;
    private ListView mLvCityList;
    private Context mContext;

    private CityAdapter mAdapter;

    private List<CityBean> mCities = new ArrayList<>();


    public CityPickController(Context context, View root) {
        mRootView = root;
        mContext = context;
        initView();
        mCities = DBManager.getInstance(mContext).getAllCities();
        mAdapter = new CityAdapter(mContext, mCities);
        mLvCityList.setAdapter(mAdapter);
    }

    private void initView() {
        mTvMask = (TextView) mRootView.findViewById(R.id.id_tv_mask);
        mLsSidebar = (LetterSideBar) mRootView.findViewById(R.id.id_ls_sidebar);
        mLvCityList = (ListView) mRootView.findViewById(R.id.id_lv_citys);
        mLsSidebar.setOverLayTextView(mTvMask);
        mLsSidebar.setOnTouchLetterListener(this);
    }


    /**
     * 处理选择字母时的回调
     *
     * @param letter
     */
    @Override
    public void onLetterSelected(String letter) {
        int position = mAdapter.getPosition(letter);
        if (position != -1)
            mLvCityList.setSelection(position);
    }
}

MainActivity


public class MainActivity extends AppCompatActivity {

    private CityPickController mController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mController = new CityPickController(this, findViewById(android.R.id.content));
    }

}

下载链接 tar.gz格式
https://pan.baidu.com/s/1nuSpEWT

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值