Android自定义滚动条——城市列表

简介: 绘制滚动条区别选中与未选择文字绘制等高间距滑动事件监听解析承载城市数据的XML文件下载XML文件解析文件适配器建立适配器类适配器子项视图效果图代码适配器绑定单向绑定


绘制滚动条



区别选中与未选择文字

private void init() {
        mLetterList = Arrays.asList(INDEX_STRING);
        mTextNormalColor = Color.parseColor("#000000");
        mTextSelectedColor = Color.parseColor("#ff0000");
    }


绘制等高间距

将一个布局高度绘制成26等份,对应26个字母

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int height = getHeight();
        int width = getWidth();
        int singleHeight = height / mLetterList.size(); // 获取每一个字母的高度
        for (int i = 0; i < mLetterList.size(); i++) {
            mPaint.setColor(mTextNormalColor);
            mPaint.setTypeface(Typeface.DEFAULT);
            mPaint.setAntiAlias(true);
            mPaint.setTextSize(32);
            // 选中的状态
            if (i == mChoose) {
                mPaint.setColor(mTextSelectedColor);
                mPaint.setFakeBoldText(true);
            }
            // x 坐标等于中间-字符串宽度的一半
            float xPos = width / 2.0f - mPaint.measureText(mLetterList.get(i)) / 2;
            float yPos = singleHeight * i + singleHeight / 2.0f;
            canvas.drawText(mLetterList.get(i), xPos, yPos, mPaint);
            mPaint.reset(); // 重置画笔
        }


滑动事件监听

 public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final float y = event.getY(); // 点击 y 坐标
        // 点击 y 坐标所占总高度的比例*b数组的长度就等于点击 b 中的个数
        final int c = (int) (y / getHeight() * mLetterList.size());
        switch (action) {
            case MotionEvent.ACTION_UP:
                if (mOnTextPositionChangedListener != null) {
                    mOnTextPositionChangedListener.onActionUp();
                }
                mChoose = -1;
                invalidate();
                break;
            default:
                if (mChoose != c) {
                    if (c >= 0 && c < mLetterList.size()) {
                        if (mOnTouchLetterChangedListener != null) {
                            mOnTouchLetterChangedListener.onTouchingLetterChanged(mLetterList.get(c));
                        }
                        mChoose = c;
                        invalidate();
                    }
                }
                if (mOnTextPositionChangedListener != null && y >= 0 && y <= getHeight()) {
                    mOnTextPositionChangedListener.onTextPositionChanged(mLetterList.get(mChoose), (int) y);
                }
                break;
        }
        return true;
    }


解析承载城市数据的XML文件



下载XML文件

从网上下载一个包含全部城市、城市代码、城市拼音首字母的文件,可以是xml格式亦或json格式


解析文件

从xml文件中取出城市数据,按城市名称、城市代码、城市首字母三个信息为一组,实例化实体类对象,并保存到列表中

private void InitCityInfo(){
        AllCityList = new ArrayList<>(  );
        String[] cityArray = getResources().getStringArray(R.array.city);
        // 侧边栏的索引字母
        ArrayList<String> indexList = new ArrayList<>();
        String curGroup = "0";
        for (int i = 0; i < cityArray.length; i += 3) {
            CityInfo cityInfo = new CityInfo(cityArray[i], cityArray[i + 1], cityArray[i + 2],
                    false, false);
            if (!cityInfo.getGroup().equals(curGroup)) {
                // 如果当前城市的 group 信息与保存的不一致, 那么就是该 group 的第一个
                cityInfo.setIsFirstInGroup(true);
                // 同时将该 group 信息添加到索引中
                indexList.add(cityInfo.getGroup());
                // 它的上一个城市就是上一个 group 的最后一个
                if (i > 0) {
                    AllCityList.get(AllCityList.size() - 1).setIsLastInGroup(true);
                }
                curGroup = cityInfo.getGroup();
            }
            AllCityList.add(cityInfo);
        }
        //AllCitiesScrollBar.setIndexText(indexList);
    }


适配器



建立适配器类

将解析出的城市数据,添加到RecyclerView子项中

public class AllCitiesReclcyerView extends RecyclerView.Adapter<AllCitiesReclcyerView.ViewHolder> {
    private List<CityInfo> mCityInfo;
    public AllCitiesReclcyerView(List<CityInfo> mCityInfo){
      this.mCityInfo = mCityInfo;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from( parent.getContext() ).inflate( R.layout.allcities_recyclerview_item,null,false );
        return new ViewHolder( view );
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
       CityInfo cityInfo = mCityInfo.get( position );
        holder.CityName.setText( cityInfo.getCityName());
    }
    @Override
    public int getItemCount() {
        return mCityInfo.size();
    }
    public int getPositionForSection(char section) {
        for (int i = 0; i < getItemCount(); i++) {
            String group = mCityInfo.get(i).getGroup();
            char firstChar = group.charAt(0);
            if (firstChar == section) {
                return i;
            }
        }
        return -1;
    }
    class ViewHolder extends RecyclerView.ViewHolder{
        private TextView CityName;
        public ViewHolder(@NonNull View itemView) {
            super( itemView );
            CityName = itemView.findViewById( R.id.CityName );
        }
    }
}


适配器子项视图

效果图

image.png


代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/AllCities"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />
    <com.example.ChooseCity.SideBar
        android:id="@+id/AllCitiesScrollBar"
        android:layout_width="20dp"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginTop="25dp"
        android:background="@drawable/sidebar_style"/>
<!--  提示文字  -->
    <TextView
        android:id="@+id/CityTipsText"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#C5C2C2"
        android:background="@drawable/scrollbar_tipstext_style"
        android:layout_toLeftOf="@+id/AllCitiesScrollBar"
        android:visibility="invisible"/>
</RelativeLayout>


适配器绑定

View AllCities = LayoutInflater.from( City.this).inflate( R.layout.choosecity_recyclerview1,null);
        AllCities_RecyclerView = AllCities.findViewById( R.id.AllCities );
        AllCitiesScrollBar = AllCities.findViewById( R.id.AllCitiesScrollBar );
        CityTipsText = AllCities.findViewById( R.id.CityTipsText );
        LinearLayoutManager manager1 = new LinearLayoutManager( City.this );
        AllCities_RecyclerView.setLayoutManager( manager1 );
        AllCitiesReclcyerView allCitiesReclcyerView = new AllCitiesReclcyerView( AllCityList );
        AllCities_RecyclerView.setAdapter( allCitiesReclcyerView );
        //分割线

此语句作用为:为每一个子项之间添加一个分割线

 AllCities_RecyclerView.addItemDecoration(new CityItemDecoration(AllCityList));


单向绑定


通过滑动滚动条,相应的改变RecyclerView子项位置;并对滑到的滚动条字母进行突出显示

AllCities_RecyclerView.addItemDecoration(new CityItemDecoration(AllCityList));
        AllCitiesScrollBar.setOnTouchLetterChangedListener( new SideBar.OnTouchingLetterChangedListener() {
            @Override
            public void onTouchingLetterChanged(String s) {
                int position = allCitiesReclcyerView.getPositionForSection(s.charAt(0));
                if (position != -1) {
                    manager1.scrollToPositionWithOffset(position, 0);
                }
            }
        } );
        AllCitiesScrollBar.setOnTextPositionChangedListener( new SideBar.OnTextPositionChangedListener() {
            @Override
            public void onTextPositionChanged(String c, int y) {
                CityTipsText.setVisibility(View.VISIBLE);
                int sideBarY = AllCitiesScrollBar.getTop();
                int topMargin = sideBarY + y - CityTipsText.getHeight() / 2;
                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) CityTipsText.getLayoutParams();
                lp.topMargin = topMargin;
                CityTipsText.setLayoutParams(lp);
                CityTipsText.setText(c);
            }
            @Override
            public void onActionUp() {
                CityTipsText.setVisibility(View.INVISIBLE);
            }
        } );
相关文章
|
4月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
435 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
10天前
|
存储 消息中间件 人工智能
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
38 4
|
4月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
|
4月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
136 0
|
4月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
4月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
217 65
Android自定义view之网易云推荐歌单界面
|
4月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
541 84
|
4月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
4月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
108 3
|
4月前
|
Android开发 开发者
Android自定义view之围棋动画(化繁为简)
本文介绍了Android自定义View的动画实现,通过两个案例拓展动态效果。第一个案例基于`drawArc`方法实现单次动画,借助布尔值控制动画流程。第二个案例以围棋动画为例,从简单的小球直线运动到双向变速运动,最终实现循环动画效果。代码结构清晰,逻辑简明,展示了如何化繁为简实现复杂动画,帮助读者拓展动态效果设计思路。文末提供完整源码,适合初学者和进阶开发者学习参考。
Android自定义view之围棋动画(化繁为简)

热门文章

最新文章