Contents involved:

  1. The first will be ListView or RecyclerView multi-layout.

  2. Customize the pinyin list on the right side of View, simply draw and set up monitoring events.

  3. Can use the pinyin4.jar third-party package to identify the first letter of Chinese characters (separately dealing with chongqing polyphonic problem).

  4. Convert the entire list of cities to {A, A… B, B city… } format, this data conversion is key!!

  5. Display the data obtained in step 3 in multiple layouts.

Difficult points:

1, the sliding problem of RecyclerView

2, RecyclerView click problem

3. Draw SideBar

Take a look at the picture first, see if it’s what you want

Implementation approach

Based on the cities and pinyin list, you can think of multiple layouts, which are nothing more than filling the list with the first letter of the city name. If you are given A set of data {A, city 1, city 2, B, city 3, city 4… } this data lets you fill your general bar, is nothing more than two layouts, pinyin and Chinese character background Settings are different on the line; The right side is a custom layout, don’t say you don’t know how to customize the layout, don’t even know how to customize the layout, this is very simple, just split the height, draw the letter by drawText(), and then swipe listen, swipe right or click to where, and scroll the left side of the list accordingly.

In fact, I have already done this through ListView, this review to use RecyclerView implementation again, found some new things, show you. I didn’t use BaseQuickAdapter this time. I used it too much and forgot how to type the original code

1. Determine the data format

First we need to determine the data format of the Bean, because multiple layouts are involved

public class ItemBean {

    private String itemName;// City name or letter A...
    private String itemType;// Type the name of the city. If it is the first letter, write "head". If it is not, fill in any other letter

    // head is 0
    public static final int TYPE_HEAD = 0;
    // Mark the city name
    public static final int TYPE_CITY = 1;
    
    public int getType(a) {
        if (itemType.equals("head")) {
            return TYPE_HEAD;
        } else {
            return TYPE_CITY;
        }
    }
	......Get Set方法  
}
Copy the code

You can see that there are two fields, one to display the city name or letter, and the other to distinguish between the city and the first letter. Here we define a getType() method that returns 0 for letters and 1 for city names

2. Organize your data

This is what we usually do with the data

<resources>
    <string-array name ="mycityarray"< <item> Beijing </item> Shanghai </item> Guangzhou </item> Tianjin </item> Tangshan </item> <item> Qinhuangdao </item> <item> Handan </item> <item> Baoding </item> <item> Zhangjiakou </item> <item> Chengde </item> The < item > cangzhou < / item > < item > langfang < / item > < item > hengshui city < / item >... </string-array> </resources>Copy the code

If you want to get the same data as us, you need to get the first letters of the names of these cities and sort them. Here I use Pinyin4J-2.5.0.jar to convert Chinese characters into pinyin

2.1 Writing tool classes

public class HanziToPinYin {
    /** * If the string string is a Chinese character, it is converted to pinyin and returned with the first letter *@param string
     * @return* /
    public static char toPinYin(String string){
        HanyuPinyinOutputFormat hanyuPinyin = new HanyuPinyinOutputFormat();
        hanyuPinyin.setCaseType(HanyuPinyinCaseType.UPPERCASE);
        hanyuPinyin.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        hanyuPinyin.setVCharType(HanyuPinyinVCharType.WITH_U_UNICODE);
        String[] pinyinArray=null;
        char hanzi = string.charAt(0);
        try {
            // Whether it is in the range of Chinese characters
            if(hanzi>=0x4e00 && hanzi<=0x9fa5){ pinyinArray = PinyinHelper.toHanyuPinyinStringArray(hanzi, hanyuPinyin); }}catch (BadHanyuPinyinOutputFormatCombination e) {
            e.printStackTrace();
        }
        // Return only the first letter of the pinyin
        return pinyinArray[0].charAt(0); }}Copy the code

2.2 Data Sorting

private List<String> cityList;      // All the city names given
private List<ItemBean> itemList;    // All subitems of item after sorting, may be cities, may be letters

// Initialize the data, sort all the cities, and add letters to them to form a new set
private void initData(a){
    
    itemList = new ArrayList<>();
    // Get all the city names
    String[] cityArray = getResources().getStringArray(R.array.mycityarray);
    cityList = Arrays.asList(cityArray);
    CityList all the cities in the list are sorted by the first letter
    Collections.sort(cityList, new CityComparator());           
	
    // Add the remaining cities
    for (int i = 0; i < cityList.size(); i++) {

        String city = cityList.get(i);
        String letter = null;                          // The current owning letter
        
        if (city.contains("Chongqing")) {
            letter = HanziToPinYin.toPinYin(Takashi "celebration") + "";
        } else {
            letter = HanziToPinYin.toPinYin(cityList.get(i)) + "";
        }

        if (letter.equals(currentLetter)) {           // Under the letter A, belongs to the current letter
            itemBean = new ItemBean();
            itemBean.setItemName(city);             // Put the Chinese characters in
            itemBean.setItemType(letter);           // Any other string that is not "head" will do
            itemList.add(itemBean);
        } else {                                 // Take the letter out as a separate item instead of under the current letter
            // Add a label (B...)
            itemBean = new ItemBean();
            itemBean.setItemName(letter);           // Put the first letter in
            itemBean.setItemType("head");          // Put the head tag inside
            currentLetter = letter;
            itemList.add(itemBean);

            // Add a city
            itemBean = new ItemBean();
            itemBean.setItemName(city);             // Put the Chinese characters in
            itemBean.setItemType(letter);           // Put the pinyin initemList.add(itemBean); }}}Copy the code

After the above steps, the original data is arranged into a set of data in the following form

{
    {itemName:"A",itemType:"head"}
    {itemName:Alxa League,itemType:"A"}
    {itemName:Pacifying city,itemType:"A"}... {itemName:Bazhong city,itemType:"B"}  
    {itemName:Baishan City,itemType:"B"}... }Copy the code

Wait, there’s collections.sort (cityList, new CityComparator()); And letter = HanziToPinYin. ToPinYin (” chongqing “) + “; Pinyin4j. jar this JAR package will convert Chongqing pinyin to Zhongqin when converting Chinese characters into pinyin, so sorting and obtaining the first letter need to be handled separately

public class CityComparator implements Comparator<String> {

    private RuleBasedCollator collator;

    public CityComparator(a) {
        collator = (RuleBasedCollator) Collator.getInstance(Locale.CHINA);
    }

    @Override
    public int compare(String lhs, String rhs) {

        lhs = lhs.replace("Chongqing".Takashi "celebration");
        rhs = rhs.replace("Chongqing".Takashi "celebration");
        CollationKey c1 = collator.getCollationKey(lhs);
        CollationKey c2 = collator.getCollationKey(rhs);

        returnc1.compareTo(c2); }}Copy the code

RuleBasedCollator = CHINA; in compare(), if there is a string with “Chongqing” on both sides, replace it with “Chongqing”. Then getCollationKey() will get the first character and compare.

Letter = HanziToPinYin. ToPinYin (” qingqing “) + “; The same is true when you get the initials, not “Chongqing” but “Chongqing”.

When you see a set of data like this, you can always fill RecyclerView with data based on multiple layouts

3. RecyclerView to fill data

Since multiple layouts are involved, there should be several Viewholders for each layout. This time I will use the original method instead of BaseQuickAdapter, which is too convenient for me to write the original

Create a new CityAdapter class that inherits recyclerView. Adapter and specify the generic type as RecyclerView.ViewHolder, which represents the inner class we defined in CityAdapter

public class CityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{.../ / letter head
    public static class HeadViewHolder extends RecyclerView.ViewHolder {
        private TextView tvHead;
        public HeadViewHolder(View itemView) {
            super(itemView); tvHead = itemView.findViewById(R.id.tv_item_head); }}/ / the city
    public static class CityViewHolder extends RecyclerView.ViewHolder {

        private TextView tvCity;
        public CityViewHolder(View itemView) {
            super(itemView); tvCity = itemView.findViewById(R.id.tv_item_city); }}}Copy the code

Override the onCreateViewHolder(), onBindViewHolder(), and getItemCount() methods. Because multiple layouts are involved, you also need to override the getItemViewType() method to determine which layout is involved

The complete code is as follows

public class CityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    / / data items
    private List<ItemBean> dataList;
    // Click the event listener interface
    private OnRecyclerViewClickListener onRecyclerViewClickListener;

    public void setOnItemClickListener(OnRecyclerViewClickListener onItemClickListener) {
        this.onRecyclerViewClickListener = onItemClickListener;
    }
    public CityAdapter(List<ItemBean> dataList) {
        this.dataList = dataList;
    }
    // Create ViewHolder instance
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        
        if (viewType == 0) {    //Head specifies the initial name
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_head, viewGroup,false);
            RecyclerView.ViewHolder headViewHolder = new HeadViewHolder(view);
            return headViewHolder;
        } else {             / / city name
            View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_city, viewGroup,false);
            RecyclerView.ViewHolder cityViewHolder = new CityViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(onRecyclerViewClickListener ! =null) { onRecyclerViewClickListener.onItemClickListener(v); }}});returncityViewHolder; }}// Assign to subitem data
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {

        int itemType = dataList.get(position).getType();
        if (itemType == 0) {
            HeadViewHolder headViewHolder = (HeadViewHolder) viewHolder;
            headViewHolder.tvHead.setText(dataList.get(position).getItemName());
        } else{ CityViewHolder cityViewHolder = (CityViewHolder) viewHolder; cityViewHolder.tvCity.setText(dataList.get(position).getItemName()); }}// Number of data items
    @Override
    public int getItemCount(a) {
        return dataList.size();
    }
    // Distinguish between layout types
    @Override
    public int getItemViewType(int position) {
        int type = dataList.get(position).getType();
        return type;
    }
    / / letter head
    public static class HeadViewHolder extends RecyclerView.ViewHolder {
        private TextView tvHead;
        public HeadViewHolder(View itemView) {
            super(itemView); tvHead = itemView.findViewById(R.id.tv_item_head); }}/ / the city
    public static class CityViewHolder extends RecyclerView.ViewHolder {
        private TextView tvCity;
        public CityViewHolder(View itemView) {
            super(itemView); tvCity = itemView.findViewById(R.id.tv_item_city); }}}Copy the code

Both item layouts place only one TextView control

Here are two things I encounter that are different from the ListView:

RecyclerView does not have setOnItemClickListener(); View = layoutinflater.from (context).inflate(r.layout.item_head, null) , and no problem was found, but this time, the Item child layout could not be horizontally spread across the parent layout. Workaround: Load the layout as follows instead

View view = LayoutInflater.from(context).inflate(R.layout.item_head, viewGroup,false);
Copy the code

(If it cannot be fully paved, it may also be the reason that RecyclerView does not specify the width and height but uses weights instead)

Listener created

public interface OnRecyclerViewClickListener {
    void onItemClickListener(View view);
}

Copy the code

4. Draw the sidebar

The customization here is as simple as defining a brush and then drawing Text on the canvas using the drawText() method.

4.1 First define the class SideBar inherited from View, rewrite the constructor, and call custom init() in the three methods; Method to initialize the brush

public class SideBar extends View {
    / / brush
    private Paint paint;
    
    public SideBar(Context context) {
        super(context);
        init();
    }
    public SideBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    // Initialize the brush tool
    private void init(a) {
        paint = new Paint();
        paint.setAntiAlias(true);/ / anti-aliasing}}Copy the code

4.2 Draw letters in onDraw()

public static String[] characters = new String[]{"❤"."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 position = -1;		// The current selected position
private int defaultTextColor = Color.parseColor("#D2D2D2");   // Default pinyin text color
private int selectedTextColor = Color.parseColor("#2DB7E1");  // The color of the selected pinyin text
   
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int height = getHeight();						// The current control height
    int width = getWidth();						 	// The current control width
    int singleHeight = height / characters.length;    // The length of each letter

    for (int i = 0; i < characters.length; i++) {
        if (i == position) {                    // Currently selected
            paint.setColor(selectedTextColor); 	// Set the color of the brush when selected
        } else {                                / / not selected
            paint.setColor(defaultTextColor);	// Sets the color of the brush when it is not selected
        }
        paint.setTextSize(textSize);			// Set the font size

        // Sets the position to draw
        float xPos = width / 2 - paint.measureText(characters[i]) / 2;
        float yPos = singleHeight * i + singleHeight;
        
        canvas.drawText(characters[i], xPos, yPos, paint);      // Draw text}}Copy the code

With these two steps, the right sidebar is drawn, but this is static, and we need to listen for its touch events if we want the sidebar to slide

4.3 Define the touch callback interface and set the listener method

// Set the touch position change listener
public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
    this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}

// Touch the interface for position changes
public interface OnTouchingLetterChangedListener {
    void onTouchingLetterChanged(int position);
}
Copy the code

4.4 Touch Events

@Override
public boolean onTouchEvent(MotionEvent event) {

    int action = event.getAction();
    float y = event.getY();
    position = (int) (y / (getHeight() / characters.length));	// Get the position of the touch

    if (position >= 0 && position < characters.length) {        
        // A callback to touch position changes
        onTouchingLetterChangedListener.onTouchingLetterChanged(position);
        
        switch (action) {
            case MotionEvent.ACTION_UP:
                setBackgroundColor(Color.TRANSPARENT);// The background changes after the finger rises
                position = -1;
                invalidate();// Redraw the control
                if(text_dialog ! =null) {
                    text_dialog.setVisibility(View.INVISIBLE);
                }
                break;
            default:// Press your finger down
                setBackgroundColor(touchedBgColor);
                invalidate();
                text_dialog.setText(characters[position]);// The letter box pops up
                break; }}else {
        setBackgroundColor(Color.TRANSPARENT);
        if(text_dialog ! =null) { text_dialog.setVisibility(View.INVISIBLE); }}return true;	// Be sure to return true to indicate that the touch event was intercepted
}
Copy the code

When the finger is up, the position is -1. When the finger is pressed, the background is changed and a letter box is displayed (the letter box here is actually a TextView, which is displayed by hiding).

5. Use in Activity

I’m not going to write that, I’m going to go ahead and sort out the data

// All subitems of item, which can be cities or letters
private List<ItemBean> itemList;    
// Whether the target item comes after the last visible item
private boolean mShouldScroll;
// Record the location of the target item (the location to move to)
private int mToPosition;

@Override
protected void onCreate(Bundle savedInstanceState) {
    // Set up the click event for the left RecyclerView Item
    cityAdapter.setOnItemClickListener(this);

     sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {
            @Override
            public void onTouchingLetterChanged(int position) {
                
                String city_label = SideBar.characters[position];      // Slide to the letter
                for (int i = 0; i < cityList.size(); i++) {
                    if (itemList.get(i).getItemName().equals(city_label)) {
                        moveToPosition(i);                         // Just roll over
// smoothMoveToPosition(recyclerView,i); // Smooth scrolling
                        tvDialog.setVisibility(View.VISIBLE);
                        break;
                    }
                    if (i == cityList.size() - 1) { tvDialog.setVisibility(View.INVISIBLE); }}}}); }// In actual combat, the page may be closed after the selection, and the current data can be returned
@Override
public void onItemClickListener(View view) {
    int position = recyclerView.getChildAdapterPosition(view);
    Toast.makeText(view.getContext(), itemList.get(position).getItemName(), Toast.LENGTH_SHORT).show();
}
Copy the code

SetSelection (Position) moves the current item to the top of the screen when you know the position to move to. And RecyclerView scrollToPosition (position) just move the item into the screen, so we need to pass scrollToPositionWithOffset () method will be placed at the top

private void moveToPosition(int position) {
    if(position ! = -1) {
        recyclerView.scrollToPosition(position);
        LinearLayoutManager mLayoutManager =
                (LinearLayoutManager) recyclerView.getLayoutManager();
        mLayoutManager.scrollToPositionWithOffset(position, 0); }}Copy the code

There is also a smooth way to scroll, as shown in Demo

6. Summary

To recap a few of my problems:

1, click the problem, ListView has setOnItemClickListener() method, and RecyclerView does not need to establish interface for listening. 2, slide problem, listView setSelection(position) slide can directly slide the item to the top of the screen, and recyclerView smoothScrollToPosition(position); It just moves it to the screen and needs to be processed again. 3, The isEnable() method of listView can set the letter Item can not be clicked, and the city name Item can be clicked, recycleView implementation (directly set up click events, is the head does not set up click events on the line) 4, Item is not full screen, load the layout of the reason

The above is all content, really do not write the article does not review will forget quickly ah, before also wrote the imitation of the United States of double RecyclerView linkage, then wrote a lot about how to slide, here you forget how to put item top, really shame, next time take time to summarize the article.

Remember to start if it helps

7. To improve

The most critical or data processing there

Part 1, sorting data, add data every time whether the include chongqing feel kind of stupid, can will all data after the filling, and chongqing, in the location specified need optimization of 2, in the sideBar setOnTouchingLetterChangedListener () method, Sliding out every time to find it from the cityList 0 first present the letter location, feel very silly, need to optimize 3, in order to facilitate the display, no encapsulate, actually also can set some such as the sidebar background such as font color are encapsulated, easy to change, but with some friend don’t custom View (I lazy), So I didn’t write it. I’ll sort it out next time.

What do you think could be improved?

Refer to the article

Android project combat (eight) : list right sidebar pinyin display effect RecyclerView will specify items to slide to the top display Java. text class CollationKey RecycleView4 kinds of positioning rolling way demonstration