添加权限和相关依赖
// ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
//汉字转换拼音
implementation 'com.belerweb:pinyin4j:2.5.1'
//图片加载
implementation 'com.github.bumptech.glide:glide:4.11.0'
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
SideBar
如果音乐按照像图片那样按照日期分类,会不好查找,所以想使用类似联系人那样按照音乐名的首字母进行分类排序,这个可以在网上找到很多类似的,我使用的是下面的这个。
https://github.com/AlexLiuSheng/AnimSideBar 可以使用 gradle 直接引用,但代码不多,就直接 copy 过来了,灵活性更高,容易自己实现自己的需求
首先自定义一个 SideBar 组件
public class SideBar extends AppCompatTextView {
public static String[] letters = 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 Paint textPaint;
private Paint bigTextPaint;
private ISideBarSelectCallBack callBack;
//事件触碰时的y轴坐标
private float eventY;
private float w;
private float sideTextWidth;
//是否重新测量宽高
private boolean isTouching = false;
//一个字母的高度
private float itemH;
//振幅
private float A = dp(50);
//波峰与bigText之间的距离
private int gapBetweenText = dp(10);
//开口数量
private int openCount = 5;
//字体缩放,基于textSize
private float fontScale = 1;
//大字大小
private float bigTextSize;
public SideBar(@NonNull Context context) {
super(context);
init(null);
}
public SideBar(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public SideBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public SideBar setFontScale(float fontScale){
this.fontScale = fontScale;
return this;
}
public void setDataResource(String[] data) {
letters = data;
invalidate();
}
public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {
this.callBack = callBack;
}
public SideBar setBigTextSize(float bigTextSize) {
this.bigTextSize = bigTextSize;
bigTextPaint.setTextSize(bigTextSize);
// invalidate();
return this;
}
public SideBar setA(float a) {
A = a;
// invalidate();
return this;
}
public SideBar setGapBetweenText(int gapBetweenText) {
this.gapBetweenText = gapBetweenText;
// invalidate();
return this;
}
public SideBar setOpenCount(int openCount) {
this.openCount = openCount;
// invalidate();
return this;
}
private void caculateAW(int height) {
// 计算单个字母高度,-2为了好看
itemH = (height) * 1.0f / letters.length;
// 开口宽度
float opendWidth = itemH * openCount;
//角速度 2PI/t 周期
w = (float) (Math.PI * 2.0f / (opendWidth * 2));
}
private int dp(int v) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (v * scale + 0.5f);
}
// 初始化
private void init(AttributeSet attrs){
if (attrs != null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);
A = dp(typedArray.getInteger(R.styleable.SideBar_A, 100));
fontScale = typedArray.getFloat(R.styleable.SideBar_fontScale, 1);
bigTextSize = typedArray.getFloat(R.styleable.SideBar_bigTextSize, getTextSize() + 2);
gapBetweenText = dp(typedArray.getInteger(R.styleable.SideBar_gapBetweenText, 50));
openCount = typedArray.getInteger(R.styleable.SideBar_openCount, 13);
} else {
bigTextSize = getTextSize() + 2;
}
// 设置画笔
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(getCurrentTextColor());
textPaint.setTextSize(getTextSize());
textPaint.setTextAlign(Paint.Align.CENTER);
bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bigTextPaint.setColor(getCurrentTextColor());
bigTextPaint.setTextSize(bigTextSize);
bigTextPaint.setTextAlign(Paint.Align.CENTER);
float sideTextHeight = textPaint.getFontMetrics().descent - textPaint.getFontMetrics().ascent;
sideTextWidth = textPaint.measureText("W");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//计算一边隆起的数量
int singleSideCount = openCount / 2;
//如果过已经触碰,且触碰的y坐标在屏幕范围内,index 就等与(触碰的y坐标值 / 每个字母的高多值)<==> 触碰的字母下标值
int index = isTouching && eventY >= 0 && eventY <= getMeasuredHeight() ? (int) Math.floor((eventY / itemH)) : -(singleSideCount + 1);
// index=Math.min(letters.length,index);
// CLog.e("index:" + index + "eventY:" + eventY);
//sidebar x轴中心点到右边边框的距离
float sideX = sideTextWidth / 2 + getPaddingRight();
// 画每一个字母
for (int i = 0; i < letters.length; i++) {
//rest textsize
textPaint.setTextSize(getTextSize());
int y = (int) (itemH * (i + 1));
int x;
if (Math.abs(i - index) > singleSideCount) { //要画字母的 y 轴位置不在弧度隆起的范围内
x = (int) (getMeasuredWidth() - sideX);
} else { //计算弧度范围内的字母坐标
float percent = eventY / itemH;
int t = (int) (i * itemH - eventY);
double v = A * Math.sin(w * t + Math.PI / 2);
//如果算出来小于字体宽度 就取字体宽度
v = Math.max(v, sideX);
x = (int) (getMeasuredWidth() - v);
//根据delta缩放字体
if (v == sideX) {
textPaint.setTextSize(getTextSize());
} else {
float delta = (Math.abs((i - percent)) / singleSideCount);
float textSize = getTextSize() + (1 - delta) * getTextSize() * fontScale;
// textSize=Math.max(textSize,getTextSize());
textPaint.setTextSize(textSize);
}
}
// 画出字母
canvas.drawText(letters[i], x, y, textPaint);
}
//判断是否有触碰的 字母 item,有就画出大字母图标
if (index != -(singleSideCount + 1)) {
index = Math.max(index, 0);
// 会有超出界限的问题,
index = Math.min(index, letters.length-1);
// 画出最大的那个字母
canvas.drawText(letters[index], getPaddingLeft() + getBigTextWidth() / 2, (int) (itemH * (index + 1)), bigTextPaint);
if (callBack != null) {
// 将触碰的字母坐标,和字母回掉给Activity界面
callBack.onSelectStr(index, letters[index]);
}
}
}
// 获得字母的宽度,这里获得 W 字母的宽度
private float getBigTextWidth() {
return bigTextPaint.measureText("W");
}
/**
* 触碰事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int startTouchX = (int) (getMeasuredWidth() - A);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //第一个触点按下之后
case MotionEvent.ACTION_MOVE: //当触点在屏幕上移动时触发,触点在屏幕上停留也是会触发的,主要是由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动)
if (event.getX() > startTouchX) {
eventY = event.getY();
if (!isTouching) {
isTouching = true;
requestLayout();
} else {
invalidate();
}
} else {
if (isTouching) {
resetDefault();
}
}
return true;
case MotionEvent.ACTION_CANCEL: //不是由用户直接触发,由系统在需要的时候触发,例如当父view通过使函数onInterceptTouchEvent()返回true,从子view拿回处理事件的控制权时,就会给子view发一个ACTION_CANCEL事件,子view就再也不会收到后续事件
case MotionEvent.ACTION_UP: //当触点松开时被触发
resetDefault();
return true;
}
return super.onTouchEvent(event);
}
private void resetDefault() {
isTouching = false;
eventY = 0;
requestLayout();
}
//设置自定义view大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
caculateAW(MeasureSpec.getSize(heightMeasureSpec));
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
viewWidth = !isTouching ? (int) (sideTextWidth + getPaddingLeft() + getPaddingRight()) : (int) (A + gapBetweenText + getBigTextWidth() + getPaddingLeft() + getPaddingRight());
}
// CLog.e("width:" + viewWidth + "height:" + MeasureSpec.getSize(heightMeasureSpec));
setMeasuredDimension(viewWidth, MeasureSpec.getSize(heightMeasureSpec));
}
public interface ISideBarSelectCallBack {
void onSelectStr(int index,String selectStr);
}
}
重点是 onDraw() 方法
这里出现了问题,有时候数组会越界,所以在前面添加判断代码,如果越界就等于数组最大值
在 res/values 文件夹下建立 attr.xml 文件,定义5个自定义的属性,这5个属性对应 SideBar 中的几个属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SideBar">
<attr name="fontScale" format="float" />
<attr name="bigTextSize" format="float" />
<attr name="openCount" format="integer" />
<attr name="A" format="integer" />
<attr name="gapBetweenText" format="integer" />
</declare-styleable>
</resources>
在 init() 方法中通过 TypedArray 类获得这些属性
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sidebar="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/yinyue_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.zwt.zwttransmit.fragment.SideBar
android:id="@+id/yinyue_sidebar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:paddingEnd="10dp"
android:textColor="@color/colorAccent"
android:textSize="12sp"
sidebar:A="40"
sidebar:gapBetweenText="50"
sidebar:openCount="5"
sidebar:fontScale="1"/>
</RelativeLayout>
RecyclerView 用于放音乐数据
public class YinYueFragment extends Fragment {
private View view;
private SideBar sideBar;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_yinyue, container,false);
initView();
return view;
}
private void initView(){
sideBar = (SideBar)view.findViewById(R.id.yinyue_sidebar);
sideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {
@Override
public void onSelectStr(int index, String selectStr) {
// 回掉,当点击 sidbar 的时候会被调用,index 表示点击的位置,selectStr 表示点击的字母u
}
});
}
@Override
public void onStart() {
super.onStart();
}
public static YinYueFragment newInstance() {
YinYueFragment fragment = new YinYueFragment();
return fragment;
}
}
这里用的是Fragment ,和 Activity 是一样的 onCreateView 就相当于 Activity 的 onCreate() 函数
获取所有的音乐数据
建立实体类
public class Music {
// 歌曲名
private String name;
// 路径
private String path;
// 专辑
private String album;
//专辑图片地址
private String albumArt;
// 作者
private String artist;
// 大小
private String size;
// 时长
private int duration;
//日期
private long time;
//首字母
private String pinyin;
// 音乐文件类型
private String contentType;
public Music(){}
public Music(String name, String path, String album, String albumArt, String artist, String size, int duration, long time, String pinyin, String contentType) {
this.name = name;
this.path = path;
this.album = album;
this.albumArt = albumArt;
this.artist = artist;
this.size = size;
this.duration = duration;
this.time = time;
this.pinyin = pinyin;
this.contentType = contentType;
}
public String getDate() {
return new SimpleDateFormat("yyyy/MM/dd a hh:mm:ss")
.format(new Date(time*1000L));
}
// 省略 getter 和 setter 方法
}
LiveData+ViewModel 获取数据
public class MusicViewModel extends ViewModel {
//用于存放音乐数据
Map<String, ArrayList<Music>> musicMap = new HashMap<>();
private MutableLiveData<Map<String, ArrayList<Music>>> musicLiveData;
String[] STORE_IMAGES = {
MediaStore.Audio.Media.TITLE,//歌曲名
MediaStore.Audio.Media.MIME_TYPE,//歌曲类型
MediaStore.Audio.Media.ALBUM,//专辑
MediaStore.Audio.Media.ALBUM_ID, //专辑ID,用于查找 专辑图片
MediaStore.Audio.Media.ARTIST,//作者
MediaStore.Audio.Media.DATA,//路径
MediaStore.Audio.Media.DURATION,//时长
MediaStore.Audio.Media.SIZE, // 大小
MediaStore.Audio.Media.DATE_ADDED //放入数据库日期
};
public MutableLiveData<Map<String, ArrayList<Music>>> getMusicLiveData() {
if (musicLiveData == null) {
musicLiveData = new MutableLiveData<Map<String, ArrayList<Music>>>();
loadMusic();
}
return musicLiveData;
}
private void loadMusic(){
new Thread(new Runnable() {
@Override
public void run() {
// 初始化Map
initMap();
Cursor cursor = MyApplication.getContext().getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
STORE_IMAGES,
null,
null,
null);
if(cursor.moveToNext()){
int titleIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int albumIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int artistIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int dataIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
int durationIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
int sizeIndex = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE);
int timeIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED);
int albumIdIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
int mimeTypeIndex = cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE);
do{
String title = cursor.getString(titleIndex);
String album = cursor.getString(albumIndex);
String artist = cursor.getString(artistIndex);
String path = cursor.getString(dataIndex);
int duration = cursor.getInt(durationIndex);
long size = cursor.getLong(sizeIndex);
long time = cursor.getLong(timeIndex);
int albumId = cursor.getInt(albumIdIndex);
String mimeType = cursor.getString(mimeTypeIndex);
//判断音乐类型
if ("audio/mpeg".equals(mimeType.trim())) {
mimeType = "mp3";
} else if ("audio/x-ms-wma".equals(mimeType.trim())) {
mimeType = "wma";
}
//根据专辑id(albumId)获取音乐专辑图片地址
String albumArt = getAlbumArt(albumId);
//获得音乐名称首字母
String pinyin = getPinyin(title);
//将音乐文件大小转换成MB格式
String sizeStr = String.valueOf(size / 1024f / 1024f).substring(0, 4) + "MB";
Music music = new Music(title, path, album, albumArt, artist, sizeStr, duration, time, pinyin, mimeType);
//根据音乐名称首字母 放入map
musicMap.get(pinyin).add(music);
}while (cursor.moveToNext());
}
musicLiveData.postValue(musicMap);
cursor.close();
}
}).start();
}
//初始化map
private void initMap(){
for(String s: SideBar.letters){
musicMap.put(s,new ArrayList<Music>());
}
}
/**
* 根据专辑的id查找专辑的图片
* return:专辑图片地址
*/
private String getAlbumArt(int album_id) {
String mUriAlbums = "content://media/external/audio/albums";
String[] projection = new String[]{"album_art"};
Cursor cur = MyApplication.getContext().getContentResolver().query(Uri.parse(mUriAlbums + "/" + Integer.toString(album_id)), projection, null, null, null);
String album_art = "";
if (cur.getCount() > 0 && cur.getColumnCount() > 0) {
cur.moveToNext();
album_art = cur.getString(0);
}
cur.close();
return album_art;
}
/**
* 获得字符串首字母
* return 返回字符串首字母
*/
private String getPinyin(String str){
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.UPPERCASE);//设置为大写形式
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);// 不用加入声调
if (str == null || str.equals(""))
return "#";
try {
char input = str.trim().charAt(0);//拿到第一个字
if (isHanZi(input)) {// 如果是汉字
//返回一个字符串数组是因为该汉字可能是多音字,此处只取第一个结果
return PinyinHelper.toHanyuPinyinStringArray(input, format)[0].substring(0,1);
}else{
if(isEnglish(input)){ // 如果是英文
return String.valueOf(input).toUpperCase(Locale.ENGLISH);
}else {
return "#";
}
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
return "#";
}
//判断是否是汉字
private boolean isHanZi(char c){
if((c >= 0x4e00)&&(c <= 0x9fbb)) {
return true;
}
return false;
}
//判断是否是英文
private boolean isEnglish(char c){
return Character.isLowerCase(c) || Character.isUpperCase(c);
}
@Override
protected void onCleared() {
super.onCleared();
// 释放资源
}
}
与图片数据获取类似,没什么好说的
显示界面
与图片相同,嵌套 RecyclerView 采用方法,
music_group_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/music_item_cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/music_group_item_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
music_child_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/music_item_cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="2dp"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/music_item_imageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_alignBottom="@+id/linearLayout"
android:layout_marginStart="11dp"
android:layout_toEndOf="@+id/list_column_v"
android:src="@drawable/img_head_portrait" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/music_item_imageView"
android:orientation="vertical"
android:layout_marginTop="5dp"
android:layout_marginLeft="15dp"
android:id="@+id/linearLayout">
<TextView
android:id="@+id/music_item_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="17sp"
android:textColor="#000000"
android:text="Something just like this" />
<TextView
android:id="@+id/music_item_textView_information"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="12sp"
android:text="Cold Player/The Chainsmokers" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
添加适配
public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.ViewHolder> {
private Context context;
private Map<String,ArrayList<Music>> musicGroupMap;
public MusicAdapter(Map<String,ArrayList<Music>> musicGroupList) {
this.musicGroupMap = musicGroupList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.music_group_item, parent, false);
final MusicAdapter.ViewHolder holder = new MusicAdapter.ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ArrayList<Music> list = musicGroupMap.get(SideBar.letters[position]);
MusicAdapter.ChildAdapter childAdapter = (MusicAdapter.ChildAdapter) holder.recyclerView.getAdapter();
if (childAdapter == null) {
GridLayoutManager manager = new GridLayoutManager(context, 1);
holder.recyclerView.setLayoutManager(manager);
holder.recyclerView.setAdapter(new MusicAdapter.ChildAdapter(list));
}else {
childAdapter.setData(list);
childAdapter.notifyDataSetChanged();
}
}
@Override
public int getItemCount() {
return musicGroupMap.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
RecyclerView recyclerView;
public ViewHolder(View view){
super(view);
cardView = (CardView)view;
recyclerView = (RecyclerView) view.findViewById(R.id.music_group_item_recyclerView);
}
}
/****************ChildAdapter*************************/
public class ChildAdapter extends RecyclerView.Adapter<MusicAdapter.ChildAdapter.ChildViewHolder>{
private ArrayList<Music> musicList;
public ChildAdapter(ArrayList<Music> musicList) {
this.musicList = musicList;
}
public void setData(ArrayList<Music> musicList) {
this.musicList = musicList;
}
@NonNull
@Override
public MusicAdapter.ChildAdapter.ChildViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.music_child_item, parent, false);
final MusicAdapter.ChildAdapter.ChildViewHolder holder = new MusicAdapter.ChildAdapter.ChildViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull MusicAdapter.ChildAdapter.ChildViewHolder holder, int position) {
Music music = musicList.get(position);
holder.textView.setText(music.getName()+"."+music.getContentType());
Glide.with(context).load(music.getAlbumArt()).into(holder.imageView);
holder.textViewInformation.setText(music.getSize()+ " | "+music.getDate());
}
@Override
public int getItemCount() {
return musicList.size();
}
class ChildViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
TextView textView;
ImageView imageView;
TextView textViewInformation;
public ChildViewHolder(View view){
super(view);
cardView = (CardView)view;
textView = (TextView) view.findViewById(R.id.music_item_textView);
imageView = (ImageView) view.findViewById(R.id.music_item_imageView);
textViewInformation = (TextView) view.findViewById(R.id.music_item_textView_information);
}
}
}
}
没什么好说的与图片类似
将适配设置到界面
public class YinYueFragment extends Fragment {
private RecyclerView recyclerView;
private MusicAdapter adapter;// 适配器
private View view;
private SideBar sideBar;
//目标项是否在最后一个可见项之后
private boolean mShouldScroll;
// 记录目标项位置
private int mToPosition;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_yinyue, container,false);
initView();
return view;
}
private void initView(){
// 设置 SideBar
sideBar = (SideBar)view.findViewById(R.id.yinyue_sidebar);
sideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {
@Override
public void onSelectStr(int index, String selectStr) {
// 点击 SideBar 回掉
smoothMoveToPosition(index);
}
});
// 实例化 RecyclerView
recyclerView = (RecyclerView)view.findViewById(R.id.yinyue_recyclerView);
final GridLayoutManager layoutManager = new GridLayoutManager(view.getContext(), 1);
recyclerView.setLayoutManager(layoutManager);
// 添加滚动事件监听器
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (mShouldScroll) {
mShouldScroll = false;
smoothMoveToPosition(mToPosition);
}
}
});
/**********获得数据**************/
final MusicViewModel model = ViewModelProviders.of(this).get(MusicViewModel.class);
model.getMusicLiveData().observe(this, new Observer<Map<String,ArrayList<Music>>>() {
@Override
public void onChanged(final Map<String,ArrayList<Music>> musicMap) {
recyclerView.addItemDecoration(new SectionNewDecoration(new SectionNewDecoration.OnTagListener(){
@Override
public String getTag(int position) {
if(musicMap.get(SideBar.letters[position]).size()>0) {
return SideBar.letters[position];
}
return "-1";
}
}));
adapter = new MusicAdapter(musicMap);
recyclerView.setAdapter(adapter);
}
});
}
@Override
public void onStart() {
super.onStart();
}
public static YinYueFragment newInstance() {
YinYueFragment fragment = new YinYueFragment();
return fragment;
}
// 将RecyclerView滚动到相应的位置
private void smoothMoveToPosition(final int position) {
// 第一个可见位置
int firstItem = recyclerView.getChildLayoutPosition(recyclerView.getChildAt(0));
// 最后一个可见位置
int lastItem = recyclerView.getChildLayoutPosition(recyclerView.getChildAt(recyclerView.getChildCount() - 1));
if (position < firstItem) {
// 如果跳转位置在第一个可见位置之前,就smoothScrollToPosition可以直接跳转
recyclerView.smoothScrollToPosition(position);
} else if (position <= lastItem) {
// 跳转位置在第一个可见项之后,最后一个可见项之前
// smoothScrollToPosition根本不会动,此时调用smoothScrollBy来滑动到指定位置
int movePosition = position - firstItem;
if (movePosition >= 0 && movePosition < recyclerView.getChildCount()) {
int top = recyclerView.getChildAt(movePosition).getTop();
recyclerView.smoothScrollBy(0, top);
}
} else {
// 如果要跳转的位置在最后可见项之后,则先调用smoothScrollToPosition将要跳转的位置滚动到可见位置
// 再通过onScrollStateChanged控制再次调用smoothMoveToPosition,执行上一个判断中的方法
recyclerView.smoothScrollToPosition(position);
mToPosition = position;
mShouldScroll = true;
}
}
}
最后实现想要的效果