Contents involved:
-
The first will be ListView or RecyclerView multi-layout.
-
Customize the pinyin list on the right side of View, simply draw and set up monitoring events.
-
Can use the pinyin4.jar third-party package to identify the first letter of Chinese characters (separately dealing with chongqing polyphonic problem).
-
Convert the entire list of cities to {A, A… B, B city… } format, this data conversion is key!!
-
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