自定义ViewPager导航控件

项目中我们经常会使用viewpager+fragment,这时候就需要一个导航控件,本文介绍如何自定义一个导航控件。

先看效果图:

先看xml布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="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:orientation="vertical"
    tools:context=".MainActivity">

    <com.example.hp.simpleviewpagerindicator.custom.SimpleIndicator
        android:id="@+id/ll_indicator"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorPrimary">
    </com.example.hp.simpleviewpagerindicator.custom.SimpleIndicator>
    <android.support.v4.view.ViewPager
        android:id="@+id/vp_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

</LinearLayout>

布局文件就一个自定义的SimpleIndicator和一个ViewPager用来存放fragment。fragment的代码很简单,就不放了。

接下来看自定的的SimpleIndicator:

public class SimpleIndicator extends LinearLayout {

    private String[] titles = new String[]{"番茄","土豆","西红柿"};
    private Paint mPaint;
    private int mTabCount;
    private float mTranslationX = 0;
    private int position = 0;
    private IndicatorListener mListener;
    private SparseArray<TextView> mList = new SparseArray<>(); //存放标题的TextView

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

    public SimpleIndicator(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);
    }

    //listener监听标题项的点击事件
    public void setListener(IndicatorListener listener){
        mListener = listener;
    }

    public void setTitles(String[] titles){
        this.titles = titles;
        mTabCount = titles.length;
        generateTitleView();
    }

    //添加标题项
    private void generateTitleView() {
        if(getChildCount()!=0)removeAllViews();

        for(int i=0;i<mTabCount;i++){
            TextView textView = new TextView(getContext());
            textView.setText(titles[i]);
            if(i==0){
                textView.setTextColor(Color.RED);
            }else {
                textView.setTextColor(Color.BLACK);
            }
            textView.setTag(i);
            LayoutParams layoutParams = new LayoutParams(0,LayoutParams.MATCH_PARENT,1);
            textView.setLayoutParams(layoutParams);
            textView.setGravity(Gravity.CENTER);
            //设置点击监听,回调listener的方法
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(mListener!=null)mListener.onClick((Integer) v.getTag());
                }
            });
            addView(textView);
            mList.put(i,textView);
        }
    }
    
    //更新标题的字体颜色
    public void refreshTextColor(int k){
        for(int i=0;i<mTabCount;i++){
            TextView textView = mList.get(i);
            if(i!=k){
                textView.setTextColor(Color.BLACK);
            }else{
                textView.setTextColor(Color.RED);
            }
        }
        position = k;
    }

    public void vpScroll(int pos, float offset){
        mTranslationX = (pos+offset)*getWidth()/mTabCount;
        //invalidate方法会执行draw过程,重绘View树。
        // View(非容器类)调用invalidate方法只会重绘自身,ViewGroup调用则会重绘整个View树。
        invalidate();
        int current = Math.round(pos+offset);
        if(current!=position)refreshTextColor(current);
    }

    /**
     * View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,
     * 对drawable调用setBounds()然后是draw(Canvas c)方法.注意的是背景drawable的实际大小会影响view组件的大小
     * drawable的实际大小通过getIntrinsicWidth()和getIntrinsicHeight()获取,
     * 当背景比较大时view组件大小等于背景drawable的大小
     * 画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法,
     * dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。
     * 值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法
     * 当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。
     * 因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,
     * 或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(),
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawLine(mTranslationX,getMeasuredHeight()-5,mTranslationX+getMeasuredWidth()/mTabCount,getMeasuredHeight()-5,mPaint);
    }

    public void setIndicatorListener(IndicatorListener listener){
        this.mListener = listener;
    }
    public interface IndicatorListener{
        void onClick(int position);
    }
}

这里注意:
①View组件的绘制会调用draw方法,draw过程中主要是先画Drawable背景,对drawable调用setBounds然后是draw犯法。注意的是背景drawable的实际大小会影响view组件的大小,当背景比较大时view组件大小等于背景drawable的大小。

②调用invalidate方法时,draw过程会调用onDraw方法,然后就是dispatchDraw方法,dispatchDraw主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw方法。但是ViewGroup容器组件的绘制,当他没有背景时直接调用的是dispatchDraw方法,而绕过了draw方法,当他有背景的时候就调用draw方法,而draw方法里包含了dispatchDraw方法的调用。因此要在ViewGroup上绘制东西的时候重写的往往是dispatchDraw方法而不是onDraw方法,或者自定制一个Drawable,重写它的draw和getIntrinsicWidth方法。

接下来看MainActivity,MainActivity是用kotlin写的:

class MainActivity : AppCompatActivity() {

    var string_array: Array<String> = arrayOf("番茄","土豆","西红柿")

    var indicator: SimpleIndicator? = null
    var mViewpager: ViewPager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        indicator = findViewById(R.id.ll_indicator)
        indicator?.setIndicatorListener(object: SimpleIndicator.IndicatorListener{
            override fun onClick(position: Int) {
                mViewpager?.currentItem = position
            }
        })
        indicator?.setTitles(string_array)

        mViewpager = findViewById(R.id.vp_fragment)
        var list: MutableList<Fragment> = mutableListOf()
        for(String in string_array){
            list.add(TestFragment())
        }
        var mAdapter: TabFragmentPagerAdapter = TabFragmentPagerAdapter(supportFragmentManager,list)
        mViewpager?.adapter = mAdapter
        mViewpager?.addOnPageChangeListener(object: ViewPager.OnPageChangeListener{
            override fun onPageScrollStateChanged(p0: Int) {

            }

            /**
             * 从第一项滚动到第二项时,滚动前position为0,滚动时position为0,offset由0增到1,滚动完成position为1
             * 从第二项滚动到第一项是,滚动前position为1,滚动时position为0,offset由1减到0,滚动完成position为0
             */
            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
                indicator?.vpScroll(position,positionOffset)
            }

            override fun onPageSelected(p0: Int) {

            }

        })
    }
}

这里注意:

ViewPager的OnPageChangeListener中的onPageScrolled方法的三个参数,看名字就知道,第一个参数是当前fragment的索引,第二个参数是滑动过程中的偏移量,第三个参数是偏移的像素值。

举个例子:这里有番茄、土豆、西红柿三个标题,也就是三个fragment,

①假设我们当前在第一项,还没开始滑动的时候,position是0,也就是第一项的索引,当我们从第一项(番茄)滑动到第二项(土豆)时,position不变,positionOffset从0增到1,滑动到第二项的时候,position变为1,positionOffset变为0

②假设我们当前在第二项,还没开始滑动的时候,position是1,也就是第二项的索引,当我们从第二项(土豆)滑动到第一项(番茄)时,position瞬间变为0,positionOffset从1减到0,滑动到第二项的时候,position为0,positionOffset变为0

单我们点击标题项时,回调到MainActivity的listener,这里调用ViewPager的setCurrentItem,setCurrentItem还是通过滑动的方法切换fragment,最终还是会调用onPageScrolled方法。

完整代码地址:https://github.com/wuxiaogui593/SimpleViewPagerIndicator

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ViewPager和RadioGroup都是Android中常用的UI控件,可以用来实现一些常见的交互式界面效果。 ViewPager是一个支持左右滑动翻页的控件,通常用来实现Tab切换、图片浏览、文章阅读等场景。ViewPager需要配合PagerAdapter(或FragmentPagerAdapter/FragmentStatePagerAdapter)一起使用,来管理ViewPager中的页面内容。ViewPager也可以通过addOnPageChangeListener()方法来添加一个页面变化监听器,监听ViewPager页面的滑动和切换事件,可以在回调方法中做一些自定义的操作。 RadioGroup是一个用于单选的控件集合,它包含了多个RadioButton,一般用来实现单选框、筛选器、分类导航等功能。RadioGroup需要通过setOnCheckedChangeListener()方法来设置选中状态变化的监听器,可以在回调方法中得到选中的RadioButton对象,然后进行相应的操作。如果需要动态添加或删除RadioButton,也可以通过ViewGroup的addView()和removeView()方法来实现。 当ViewPager和RadioGroup结合使用时,可以实现Tab样式的页面切换效果,即ViewPager的每个页面对应一个RadioGroup中的一个RadioButton,当用户点击某个RadioButton时,ViewPager会跳转到对应的页面。这种交互式的界面效果也比较常见,例如资讯类App中的首页、今日头条等。实现这种效果,需要在RadioButton的选中状态变化监听方法中,通过ViewPager的setCurrentItem()方法来实现页面的切换。而在ViewPager页面变化监听方法中,则需要通过RadioGroup的check()方法来更新RadioButton的选中状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值