无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)



自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果。的确HorizontalScrollView可以实现Gallery的效果。


其实HorizontalScrollView与ScrollView使用的模式一样,里面的内容必须包含在一个布局里面比如LinearLayout等,不能在其标签内直接放入TextView等子控件,必须被包裹。


那么我们上图看看我们最终要实现的效果图:


无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第1张图片     无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第2张图片



我们在单个viewPager显示页面放入要获取的新闻网址用于区分功能实现效果。其参数(新闻网址)是创建的时候就传进入进去的,避免写死后,不能增加横向滑动菜单数,看看网易旁边的按钮。如下图:


无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第3张图片


这样方便我们扩展新闻,毕竟你不可能,也不应该对每一个横向标题固定创建一个Fragment那将是耗时且低效率的,我们选择动态增加。好了,我们开始实现我们应该实现的功能吧。


1.思路构思


编程中代码都是次要的,思路才是最重要的,没有思路,俗称码农。先思路后代码才能称为程序员。


看着这个横向滑动菜单,你想到了什么呢?会用到哪些技术?


首先我们可以看到,要想实现这个无限添加的横向滑动的菜单,和怎么都不变的下划线滑动效果,这里最重要的就是算法了。现在默认只有五个菜单,如果你用if else if或者switch case那么,当有100个横向滑动菜单的时候,你不会if100次吧,也不会case100次吧!这里我们先来讲讲这高深莫测的算法。(如果你数据结构里面的算法全部都掌握了,你可以直接跳过,这里用到的算法,就你可以把它想成是一个链表,下划线就是指针,指向哪里。其实就这么简单)


Ⅰ图解算法,从左到右:


无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第4张图片


①从头条标题滑动到宜昌标题


其结果并不是想象中的滑动宜昌标题的间距,而是滑过头条,也就是说头条滑动到宜昌其实滑动的距离是头条控件的大小。


②同理,从头条直接滑动到轻松一刻。也是滑动的头条加宜昌控件的大小和控件的间距的和。


我们假设两个字的控件大小为90,四个字的控件大小为150,那么①滑动了90,②滑动了180。


我们知道每当ViewPager变动的时候其会触发ViewPager.OnPageChangeListener接口。当我们滑动到某个控件(不特别说明就是横向滑动菜单的TextView控件)的时候,该接口下的onPageSelected(int position)会告诉我们所引值。在这里我们可以设置下划线的滑动效果。


还有因为我们不知道是从哪个标题滑动到哪个标题,我们必须在自定义的HorizontalScrollView控件中增加一个成员变量,保存当前菜单的位置,也就是滑动的时候是从哪个控件开始滑动的。我们这里用到的变量为(mLastPosition)。


我们滑动下划线,用到的是平移动画,因为只是水平滑动,用到的只有两个参数,一个是滑动的起点,另一个就是滑动的终点。所以计算起点和终点,会用到两个循环。算法的代码如下:


for (int j = 0; j < mLastPosition; j++) {
    scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);//控件大小+控件间距(dp转换成px)
}
for (int j=0;j<i;j++){
    scrollEndX=scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
}
Log.i("liyuanjinglyj", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
slideview(scrollStartX, scrollEndX);



Ⅱ图解算法,从右到左:


无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第5张图片


①从科技菜单到财经菜单


前头我们假设过控件的大小,这里我们直接说结果,从科技到财经滑动了90,但是滑动的值是从科技的开头,也就是前面四个控件的大小为420,到财经开头,也就是330。平移动画的前二个参数就是(420,330)。


②从科技菜单到宜昌菜单


同理,也就是科技开头为前面4个控件大小加间距的和为420,到宜昌开头为头条大小90。可以看出来,这里我们也需要2个循环,计算控件离屏幕左边的距离。


算法代码如下:


for (int j = 0; j <mLastPosition; j++) {
    scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);//控件大小+控件间距(dp转换成px)
}
for (int j=0;j<i;j++){
    scrollEndX = scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
}
Log.i("liyuanjinglyjfanxiang", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
slideview(scrollStartX, scrollEndX);



2.自定义HorizontalScrollView


怎么设计我们的HorizontalScrollView呢?


我们知道当我们点击HorizontalScrollView中的标题也就是TextView的时候,会触发ViewPager的跳转。而HorizontalScrollView是自定义的空间,当其点击之中的TextView时候,怎么让NewsFragment中的ViewPager跳转呢?


答案很简单,设置一个回调函数,当点击TextView默认会调用实现回调函数的具体方法。


那么我们开始设计我们的HorizontalScrollView。


Ⅰ首先,我们需要用到哪些成员变量


代码如下:


private LayoutInflater inflater;//加载布局进来用的
private View view;//布局View
private LinearLayout titleAllTxt;//标题布局
private List<TextView> titleScroll = new ArrayList<>();//标题数组
private ImageView iv_nav_indicator;//下划线
private static int mLastPosition = 0;//当前选中标题索引
private OnItemClickListener mOnClickListener;//监听函数



说明:


LayoutInflater:加载所有的标题也就是TextView到HorizontalScrollView的横向滑动菜单中,使其可以滑动。

titleScroll:HorizontalScrollView每增加一个TextView默认添加到List<TextView>中。

mLastPosition:方法一中讲过,记录当前TextView的索引。

mOnClickListener:回调接口,通知viewPager跳转。

iv_nav_indicator:下划线控件。

view:添加到HorizontalScrollView的布局文件。

titleAllTxt:除下划线外的布局文件,也就是所有的标题。


Ⅱ定义回调接口,通知viewPager跳转


代码如下:


public interface OnItemClickListener {
    void onClick(int pos);
}



ⅢHorizontalScrollView实现View.OnClickListener接口


代码如下:


@Override
public void onClick(View v) {
    mOnClickListener.onClick(titleAllTxt.indexOfChild(v));
}



其作用是方便设置TextView标题的点击事件。


Ⅳ初始化HorizontalScrollView滑动菜单界面


代码如下:


/**
 * 初始化加载进来的布局
 */
private void initView() {
    this.inflater = LayoutInflater.from(getContext());
    view = this.inflater.inflate(R.layout.news_title_horizontalscrollview_layout, null);
    this.titleAllTxt = (LinearLayout) view.findViewById(R.id.news_title_horizontalscrollview_titletxt_layout);
    for (int i = 0; i < this.titleAllTxt.getChildCount(); i++) {
        this.titleScroll.add((TextView) this.titleAllTxt.getChildAt(i));
        ((TextView) this.titleAllTxt.getChildAt(i)).setOnClickListener(this);
    }
    this.iv_nav_indicator = (ImageView) view.findViewById(R.id.iv_nav_indicator);
    int w = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    int h = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    this.titleScroll.get(0).measure(w, h);
    LinearLayout.LayoutParams imageParams = (LinearLayout.LayoutParams) this.iv_nav_indicator.getLayoutParams();
    imageParams.width = this.titleScroll.get(0).getMeasuredWidth() + 20;
    imageParams.height = 5;
    this.iv_nav_indicator.setLayoutParams(imageParams);
    addView(view);
}



其作用初始化界面,使我们看到如本博文开头的界面,并设置TextView的点击事件。


Ⅴ下划线滑动动画


代码如下:


/**
 * 滑动动画
 * @param p1 起始
 * @param p2 终点
 */
public void slideview(float p1, float p2) {
    TranslateAnimation animation = new TranslateAnimation(p1, p2, 0, 0);
    animation.setInterpolator(new OvershootInterpolator());
    animation.setDuration(300);
    animation.setFillEnabled(true);
    animation.setFillAfter(true);
    iv_nav_indicator.startAnimation(animation);
}



默认两个参数,一个起点,一个终点。这里与前面两篇博文XML动画形成鲜明的对比,这就是动态加载动画。两种动画实现方式都讲到了。


Ⅵ动态添加标题


代码如下:


/**
 * 动态添加标题
 * @param text 标题文字
 * @param context 上下文
 */
public void addTextViewTitle(String text, Context context) {
    TextView textView = new TextView(context);
    textView.setText(text);
    textView.setTextColor(Color.BLACK);
    textView.setClickable(true);
    textView.setTextSize(20.0f);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    params.setMargins(10, 0, 10, 0);
    textView.setLayoutParams(params);
    textView.setOnClickListener(this);
    this.titleAllTxt.addView(textView);
    this.titleScroll.add(textView);
    new Handler().post(new Runnable() {
        public void run() {
            try {
                Thread.sleep(10);
                int left = titleAllTxt.getMeasuredWidth() - getWidth();
                if (left < 0) {
                    left = 0;
                }
                scrollTo(left, 0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}



这里为什么要延迟10毫秒后执行,因为刚添加一个TextView标题到HorizontalScrollView中,界面还没来的及刷新,你这个时候如果直接跳转到标题位置的末端。那么就会出现跳转到新添加标题位置的前面那个标题,也就是刚才整个标题栏的大小,后出现界面更新,虽然肉眼觉得这是一起完成的,其实是先后错位了,但你眼睛没有发觉,但程序很诚实。


Ⅶviewpager跳转后下划线联动


代码如下:


/**
 * viewPager跳转之下划线联动方法
 * @param position 跳转后的索引
 */
public void setPagerChangeListenerToTextView(int position) {
    int scrollStartX = 0;//动画其实位置
    int scrollEndX = 0;//动画结束位置
    for (int i = 0; i < this.titleScroll.size(); i++) {//循环是为了设置其他标题为黑色
        if (i == position) {
            if (mLastPosition < i) {//判断滑动方向,里面为左向右
                for (int j = 0; j < mLastPosition; j++) {
                    scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                }
                for (int j=0;j<i;j++){
                    scrollEndX=scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                }
                Log.i("liyuanjinglyj", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
                slideview(scrollStartX, scrollEndX);
            } else {//判断滑动方向,里面为右向左
                for (int j = 0; j <mLastPosition; j++) {
                    scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                }
                for (int j=0;j<i;j++){
                    scrollEndX = scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                }
                Log.i("liyuanjinglyjfanxiang", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
                slideview(scrollStartX, scrollEndX);
            }
            LinearLayout.LayoutParams imageParams = (LinearLayout.LayoutParams) this.iv_nav_indicator.getLayoutParams();
            imageParams.width = this.titleScroll.get(i).getWidth() + ApplyUtils.dip2px(getContext(), 20);
            imageParams.height = 5;
            this.iv_nav_indicator.setLayoutParams(imageParams);
            this.titleScroll.get(i).setTextColor(Color.RED);
            this.mLastPosition = position;//设置当前的标题索引
        } else {
            this.titleScroll.get(i).setTextColor(Color.BLACK);
        }
    }
}



这个代码的构思在本文第一节已经讲清楚了,这里不在赘述。


3.ViewPager的FragmentPagerAdapter


其代码在adapter包中。


没在书上见过吧!呵呵,这是书上没有的,技术嘛,还是跟着文档走。不过,好书也是有讲到的,只是很少。


其布局全是Fragment文件。开发网易新闻APP到现在你发现没有,除了一个Activity,我这里没用到第二个Activity,全部都是Fragment。这样对系统来说,负担要小的多。不过,后续还是会有第二个Activity,也就是新闻详情页面会用到一个,也仅仅就两个而已。


代码简单如PagerAdapter。其就是PagerAdapter的替代品。谷歌推荐你使用这个,只是换个名字而已。用法一模一样。


代码如下:


public class MyPagerAdapter extends FragmentPagerAdapter {
    public List<Fragment> fragment;
    public MyPagerAdapter(FragmentManager fm) {
        super(fm);

    }
    public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        this.fragment = fragments;
    }
    @Override
    public int getCount() {
        return fragment.size();
    }
    @Override
    public Fragment getItem(int position) {
        return fragment.get(position);
    }
}



我们知道,我们的横向标题是动态添加的,也可以无限增长,那么如果每个标题固定写一个Fragment那么有100个,那还不得累死我们这些程序员吗?


网络爬虫的时候就讲到过,其每个新闻页面都大同小异,除了获取的新闻网址不同外,其他的基本都是相同的,那么只要在动态创建Fragment的时候传进去该页面获取新闻的网址,那么就可以实现动态无限横向标题栏了。


我们用到的Fragment的代码如下,仅仅在构造函数中添加一个网址参数,其Fragment在fragment包中:


public class NewsViewPagerFragment extends Fragment{
    private String urlStr;
    public NewsViewPagerFragment() {
    }
    public NewsViewPagerFragment(String url) {
        this.urlStr=url;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.news_viewpager_fragment_main,container,false);
        TextView urlTxt=(TextView)view.findViewById(R.id.news_viewpager_fragment_main_urltxt);
        urlTxt.setText(this.urlStr);
        return view;
    }
}



4.初始化ViewPager


因为整个布局都是在NewsFragment中显示的,所以我们初始化ViewPager的事情也在NewsFragment中。


代码如下:


/***
 * 初始化ViewPager
 */
private void initViewPager(){
    this.fragmentList=new ArrayList<>();
    fragmentList.add(new NewsViewPagerFragment("http://news.163.com/mobile/"));
    fragmentList.add(new NewsViewPagerFragment("http://news.163.com/mobile/"));
    fragmentList.add(new NewsViewPagerFragment("http://3g.163.com/ntes/special/00340D52/3gtouchlist.html?docid=A9O2HAB6jiying&amp;title=%E8%BD%BB%E6%9D%BE%E4%B8%80%E5%88%BB"));
    fragmentList.add(new NewsViewPagerFragment("http://3g.163.com/touch/money/"));
    fragmentList.add(new NewsViewPagerFragment("http://3g.163.com/touch/tech/"));
    pagerAdapter=new MyPagerAdapter(getActivity().getSupportFragmentManager(), fragmentList);
    this.viewPager.setAdapter(pagerAdapter);//设置adapter
    this.viewPager.setCurrentItem(0);//设置当前显示的页面
    this.viewPager.addOnPageChangeListener(new OnPagerChangeListener());//设置监听函数
}


每个Fragment都是动态创建的,每个ViewPager的item都是一个新闻网址。所以找到网易手机访问首页的该标签的网址,添加到参数中创建Fragment。


下面我们来看看ViewPager的监听函数:

private class OnPagerChangeListener implements ViewPager.OnPageChangeListener{
	//滑动中执行
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }
	//滑动到某个页面执行
    @Override
    public void onPageSelected(int position) {
        titleScroll.setPagerChangeListenerToTextView(position);//联动HorizontalScrollView
    }
	//状态改变时候调用
    @Override
    public void onPageScrollStateChanged(int state) {

    }
}



5.HorizontalScrollView设置回调监听



我们不仅让ViewPager改变的时候HorizontalScrollView有所改变,我们也要让HorizontalScrollView改变的时候ViewPager也要有所改变。


我们看看NewsFragment布局文件中关于ViewPager与HorizontalScrollView的布局是怎么写的,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/lyj_title_bar_layout"/>

    <include layout="@layout/important_news_layout"/>

    <include layout="@layout/weather_layout"/>

    <LinearLayout
        android:id="@+id/news_fragment_main_horizontalscrollview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lyj_title_bar_layout_main"
        android:orientation="horizontal">

        <com.example.liyuanjing.myview.NewsTitleHorizontalScrollView
            android:id="@+id/myHorizeontal"
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:fillViewport="true"
            android:layout_below="@+id/news_activity_main_title_down"
            android:layout_weight="5"
            android:scrollbars="none">

        </com.example.liyuanjing.myview.NewsTitleHorizontalScrollView>

        <LinearLayout
            android:id="@+id/news_fragment_main_layout_addtitle"
            android:layout_width="0dp"
            android:layout_height="28dp"
            android:layout_weight="1"
            android:clickable="true"
            android:gravity="center"
            android:orientation="vertical">

            <ImageButton
                android:layout_width="20dp"
                android:layout_height="11dp"
                android:scaleType="fitXY"
                android:clickable="false"
                android:duplicateParentState="true"
                android:background="@drawable/biz_news_column_edit_arrow_down"/>
        </LinearLayout>
    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/vPager"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/news_fragment_main_horizontalscrollview"
        android:layout_gravity="center"
        android:layout_weight="1.0"
        android:flipInterval="30"
        android:persistentDrawingCache="animation" />
</RelativeLayout>


大多数布局文件代码是不需要解释的,不过有几点还是要说明一下:


①android:scrollbars="none"默认HorizontalScrollView是有下划线的,就像ScrollView一样。一个有粗有长的横向滑动条。


②横向滑动菜单旁边有个箭头向下的按钮,其被包裹在LinearLayout中,该ImageButton的点击事件由其父Linearlayout拦截,并处理,这里我们没有实现其界面但点击这个按钮,默认会添加一个标题在横向滑动菜单中。当然这不是永久的,等把优化ListView加载新闻讲解后,就会来完善这个箭头界面和永久添加菜单。


③android:persistentDrawingCache="animation":定义绘图的高速缓存的持久性。 绘图缓存可能由一个ViewGroup在特定情况下为其所有的子类启用,例如在一个滚动的过程中。 此属性可以保留在内存中的缓存后其初始的使用。 坚持缓存会消耗更多的内存,但可能会阻止频繁的垃圾回收是反复创建缓存。 默认情况下持续存在设置为滚动。这里表示在布局动画之后,该绘图缓存一直保持。


④android:flipInterval="30":视图间切换的时间间隔。其他的属性前面都提到过,这里不在过多的解释。


所以NewsFragment中代码如下:


//初始化viewPager代码段
this.titleScroll=(NewsTitleHorizontalScrollView)view.findViewById(R.id.myHorizeontal);
this.viewPager=(ViewPager)view.findViewById(R.id.vPager);
this.addTitleLayout=(LinearLayout)view.findViewById(R.id.news_fragment_main_layout_addtitle);
initViewPager();
//添加标题
this.addTitleLayout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        titleScroll.addTextViewTitle("美女", getActivity());
        fragmentList.add(new NewsViewPagerFragment("http://news.163.com/mobile/"));
        pagerAdapter = new MyPagerAdapter(getActivity().getSupportFragmentManager(), fragmentList);
        viewPager.setAdapter(pagerAdapter);
        pagerAdapter.notifyDataSetChanged();
        new Handler().post(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10);
                    viewPager.setCurrentItem(fragmentList.size() - 1);//定位到最后一个ViewPager的item,并滑动菜单到最后一个刚添加的美女标题
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
});
//实现监听
this.titleScroll.setOnItemClickListener(new NewsTitleHorizontalScrollView.OnItemClickListener() {
    @Override
    public void onClick(int pos) {
        viewPager.setCurrentItem(pos);//设置标题栏TextView的点击事件
    }
});


这里默认可以添加标题,不过都是美女标题,这里也用到了handle.post方法。同样停顿的10毫秒,也是为了等待界面的刷新,不然下划线获取不到刚刚添加的控件的宽度。


6.自定义HorizontalScrollView


前面细分的讲解了HorizontalScrollView的实现,这里完整的看一下代码,首先HorizontalScrollView动态的添加了布局文件,其布局文件news_title_horizontalscrollview_layout.xml代码如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/news_title_horizontalscrollview_titletxt_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/ttTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:clickable="true"
            android:textColor="@android:color/black"
            android:text="@string/news_title_ttTxt"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/ycTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:clickable="true"
            android:textColor="@android:color/black"
            android:text="@string/news_title_ycTxt"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/qsykTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:clickable="true"
            android:textColor="@android:color/black"
            android:text="@string/news_title_qsycTxt"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/caijinTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:clickable="true"
            android:textColor="@android:color/black"
            android:text="@string/news_title_caijinTxt"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/kejiTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:clickable="true"
            android:textColor="@android:color/black"
            android:text="@string/news_title_kejiTxt"
            android:textSize="20dp" />
    </LinearLayout>

    <ImageView
        android:id="@+id/iv_nav_indicator"
        android:layout_width="1dip"
        android:layout_height="5dip"
        android:gravity="center"
        android:background="#FF0000"
        android:scaleType="matrix" />

</LinearLayout>



一个标题栏,一个下划线,标题栏里面有五个TextView。


最后亮出一下完整的NewsTitleHorizontalScrollView代码如下:


public class NewsTitleHorizontalScrollView extends HorizontalScrollView implements View.OnClickListener{
    private LayoutInflater inflater;//加载布局进来用的
    private View view;//布局View
    private LinearLayout titleAllTxt;//标题布局
    private List<TextView> titleScroll = new ArrayList<>();//标题数组
    private ImageView iv_nav_indicator;//下划线
    private static int mLastPosition = 0;//当前选中标题索引
    private OnItemClickListener mOnClickListener;//监听函数

    @Override
    public void onClick(View v) {
        mOnClickListener.onClick(titleAllTxt.indexOfChild(v));
    }

    public interface OnItemClickListener {
        void onClick(int pos);
    }

    public void setOnItemClickListener(OnItemClickListener mOnClickListener) {
        this.mOnClickListener = mOnClickListener;
    }


    public NewsTitleHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    /**
     * 初始化加载进来的布局
     */
    private void initView() {
        this.inflater = LayoutInflater.from(getContext());
        view = this.inflater.inflate(R.layout.news_title_horizontalscrollview_layout, null);
        this.titleAllTxt = (LinearLayout) view.findViewById(R.id.news_title_horizontalscrollview_titletxt_layout);
        for (int i = 0; i < this.titleAllTxt.getChildCount(); i++) {
            this.titleScroll.add((TextView) this.titleAllTxt.getChildAt(i));
            ((TextView) this.titleAllTxt.getChildAt(i)).setOnClickListener(this);
        }
        this.iv_nav_indicator = (ImageView) view.findViewById(R.id.iv_nav_indicator);
        int w = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int h = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        this.titleScroll.get(0).measure(w, h);
        LinearLayout.LayoutParams imageParams = (LinearLayout.LayoutParams) this.iv_nav_indicator.getLayoutParams();
        imageParams.width = this.titleScroll.get(0).getMeasuredWidth() + 20;
        imageParams.height = 5;
        this.iv_nav_indicator.setLayoutParams(imageParams);
        addView(view);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
    }


    /**
     * viewPager跳转之下划线联动方法
     * @param position 跳转后的索引
     */
    public void setPagerChangeListenerToTextView(int position) {
        int scrollStartX = 0;//动画其实位置
        int scrollEndX = 0;//动画结束位置
        for (int i = 0; i < this.titleScroll.size(); i++) {//循环是为了设置其他标题为黑色
            if (i == position) {
                if (mLastPosition < i) {//判断滑动方向,里面为左向右
                    for (int j = 0; j < mLastPosition; j++) {
                        scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                    }
                    for (int j=0;j<i;j++){
                        scrollEndX=scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                    }
                    Log.i("liyuanjinglyj", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
                    slideview(scrollStartX, scrollEndX);
                } else {//判断滑动方向,里面为右向左
                    for (int j = 0; j <mLastPosition; j++) {
                        scrollStartX += this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                    }
                    for (int j=0;j<i;j++){
                        scrollEndX = scrollEndX + this.titleScroll.get(j).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                    }
                    Log.i("liyuanjinglyjfanxiang", "scrollStartX=" + String.valueOf(scrollStartX) + "----scrollEndX" + String.valueOf(scrollEndX));
                    slideview(scrollStartX, scrollEndX);
                }
                LinearLayout.LayoutParams imageParams = (LinearLayout.LayoutParams) this.iv_nav_indicator.getLayoutParams();
                imageParams.width = this.titleScroll.get(i).getWidth() + ApplyUtils.dip2px(getContext(), 20);
                imageParams.height = 5;
                this.iv_nav_indicator.setLayoutParams(imageParams);
                this.titleScroll.get(i).setTextColor(Color.RED);
                this.mLastPosition = position;//设置当前的标题索引
            } else {
                this.titleScroll.get(i).setTextColor(Color.BLACK);
            }
        }
    }

    /**
     * 滑动动画
     * @param p1 起始
     * @param p2 终点
     */
    public void slideview(float p1, float p2) {
        TranslateAnimation animation = new TranslateAnimation(p1, p2, 0, 0);
        animation.setInterpolator(new OvershootInterpolator());
        animation.setDuration(300);
        animation.setFillEnabled(true);
        animation.setFillAfter(true);
        iv_nav_indicator.startAnimation(animation);
    }

    /**
     * 动态添加标题
     * @param text 标题文字
     * @param context 上下文
     */
    public void addTextViewTitle(String text, Context context) {
        TextView textView = new TextView(context);
        textView.setText(text);
        textView.setTextColor(Color.BLACK);
        textView.setClickable(true);
        textView.setTextSize(20.0f);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        params.setMargins(10, 0, 10, 0);
        textView.setLayoutParams(params);
        textView.setOnClickListener(this);
        this.titleAllTxt.addView(textView);
        this.titleScroll.add(textView);
        new Handler().post(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10);
                    int left = titleAllTxt.getMeasuredWidth() - getWidth();
                    if (left < 0) {
                        left = 0;
                    }
                    scrollTo(left, 0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}



横向滑动菜单的功能我们就全部完成了,欢迎下载源码:


http://download.csdn.net/detail/liyuanjinglyj/9241387


其还有功能有待后续扩展,如滑动菜单怎么在手指滑动中就开始滑动下划线,并保持联动。添加标题永久化。超过界面标题自动跳转到屏幕中显示标题等。后续在实现下拉箭头添加菜单的时候会讲,你也可以自己试试。毕竟程序员是练出来的,不是看出来的。


离开两到三个星期后,接载新闻数据库设计及优化ListView等。


添加菜单后的运行界面:


无限横向滑动菜单(自定义HorizontalScrollView+ViewPager)_第6张图片



你可能感兴趣的:(android,viewpager)