ViewPagerIndicator 仿网易新闻tab或是知乎的发现中的tab

tab中经常有用到如网易新闻中那样的效果,要求能滑动,下面有指示,当tab页超出屏幕下面的内容也要跟着移动。

指示器的效果很多人都会用到TabPageIndicator,这里我们参考http://blog.csdn.net/lmj623565791/article/details/42160391 来写下自己的指示器。

如图:

总共有三个部分组成,

HorizontalScrollView里有个LinearLayout可以添加我们的view,

ViewPagerIndicator我们的指示器

ViewPager来放内容fragment

布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical" >

    <HorizontalScrollView
        android:id="@+id/hsv"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:scrollbars="@null" >

        <LinearLayout
            android:id="@+id/ll1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal" >
        </LinearLayout>
    </HorizontalScrollView>

    <com.example.demotab.ViewPagerIndicator
        android:id="@+id/indicator"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:background="#000000"
        android:orientation="horizontal"
        >
    </com.example.demotab.ViewPagerIndicator>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </android.support.v4.view.ViewPager>

</LinearLayout>

2.重点是ViewPagerIndicator的逻辑处理,我们这里要用到自定义控件的知识,不懂可以查看我以前的关于怎么自定义控件的文章,指示器就是用一个LinearLayout

package com.example.demotab;

import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

/**
 *
 *
 * @author chaoyue
 *
 */
public class ViewPagerIndicator extends LinearLayout {
	/**
	 * 绘制三角形的画笔
	 */
	private Paint mPaint;
	/**
	 * path构成一个三角形
	 */
	private Path mPath;
	/**
	 * 三角形的宽度
	 */
	private int mTriangleWidth;
	/**
	 * 三角形的高度
	 */
	private int mTriangleHeight;

	/**
	 * 三角形的宽度为单个Tab的1/6
	 */
	private static final float RADIO_TRIANGEL = 1.0f / 6;
	/**
	 * 三角形的最大宽度
	 */
	private final int DIMENSION_TRIANGEL_WIDTH = (int) (getScreenWidth() / 3 * RADIO_TRIANGEL);

	/**
	 * 初始时,三角形指示器的偏移量
	 */
	private int mInitTranslationX;
	/**
	 * 手指滑动时的偏移量
	 */
	private float mTranslationX;

	/**
	 * tab上的内容
	 */
	private List<View> mTabTitles;
	/**
	 * 与之绑定的ViewPager
	 */
	public ViewPager mViewPager;

	/**
	 * 子view都为正方形宽度
	 */
	private int childViewWidth;

	/**
	 * 绑定的HorizontalScrollView
	 */
	private HorizontalScrollView horizontalScrollView;

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

	public ViewPagerIndicator(Context context, AttributeSet attrs) {
		super(context, attrs);

		// 初始化画笔
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.parseColor("#ffffffff"));
		mPaint.setStyle(Style.FILL);
		mPaint.setPathEffect(new CornerPathEffect(3));

		if (childViewWidth == 0) {
			childViewWidth = 300;
		}
	}

	public void setChildViewWidth(int childViewWidth) {
		this.childViewWidth = childViewWidth;
	}

	public void setHorizontalScrollView(
			HorizontalScrollView horizontalScrollView) {
		this.horizontalScrollView = horizontalScrollView;
	}

	/**
	 * 绘制指示器
	 */
	@Override
	protected void dispatchDraw(Canvas canvas) {
		canvas.save();
		// 画笔平移到正确的位置
		canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1);
		canvas.drawPath(mPath, mPaint);
		canvas.restore();

		super.dispatchDraw(canvas);
	}

	/**
	 * 初始化三角形的宽度
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		mTriangleWidth = (int) (w / 4 * RADIO_TRIANGEL);// 1/6 of
																		// width
		mTriangleWidth = Math.min(DIMENSION_TRIANGEL_WIDTH, mTriangleWidth);

		// 初始化三角形
		initTriangle();

		// 初始时的偏移量
		mInitTranslationX = childViewWidth / 2 - mTriangleWidth / 2;
	}

	/**
	 * 设置tab的标题内容 可选,可以自己在布局文件中写死
	 *
	 * @param datas
	 */
	public void setTabItemTitles(List<View> datas) {
		// 如果传入的list有值,则移除布局文件中设置的view
		if (datas != null && datas.size() > 0) {
			this.removeAllViews();
			this.mTabTitles = datas;
			for (int i = 0; i < datas.size(); i++) {

				addView(datas.get(i), childViewWidth, childViewWidth);

			}
			// 设置item的click事件
			setItemClickEvent();
		}

	}

	/**
	 * 对外的ViewPager的回调接口
	 *
	 * @author zhy
	 *
	 */
	public interface PageChangeListener {
		public void onPageScrolled(int position, float positionOffset,
				int positionOffsetPixels);

		public void onPageSelected(int position);

		public void onPageScrollStateChanged(int state);
	}

	// 对外的ViewPager的回调接口
	private PageChangeListener onPageChangeListener;

	// 对外的ViewPager的回调接口的设置
	public void setOnPageChangeListener(PageChangeListener pageChangeListener) {
		this.onPageChangeListener = pageChangeListener;
	}

	// 设置关联的ViewPager
	public void setViewPager(ViewPager mViewPager, int pos) {
		this.mViewPager = mViewPager;

		mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
			@Override
			public void onPageSelected(int position) {

				// 回调
				if (onPageChangeListener != null) {
					onPageChangeListener.onPageSelected(position);
				}
			}

			@Override
			public void onPageScrolled(int position, float positionOffset,
					int positionOffsetPixels) {
				// 滚动
				scroll(position, positionOffset);

				// 回调
				if (onPageChangeListener != null) {
					onPageChangeListener.onPageScrolled(position,
							positionOffset, positionOffsetPixels);
				}

			}

			@Override
			public void onPageScrollStateChanged(int state) {
				// 回调
				if (onPageChangeListener != null) {
					onPageChangeListener.onPageScrollStateChanged(state);
				}

			}
		});
		// 设置当前页
		mViewPager.setCurrentItem(pos);
		// 高亮
	}

	/**
	 * 设置点击事件
	 */
	public void setItemClickEvent() {
		LinearLayout layout = (LinearLayout) horizontalScrollView.getChildAt(0);
		int cCount = layout.getChildCount();
		for (int i = 0; i < cCount; i++) {
			final int j = i;
			View view = layout.getChildAt(i);
			view.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					mViewPager.setCurrentItem(j);
				}
			});
		}
	}

	/**
	 * 初始化三角形指示器
	 */
	private void initTriangle() {
		mPath = new Path();

		mTriangleHeight = (int) (mTriangleWidth / 2 / Math.sqrt(2));
		mPath.moveTo(0, 0);
		mPath.lineTo(mTriangleWidth, 0);
		mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);
		mPath.close();
	}

	/**
	 * 指示器跟随手指滚动,以及容器滚动
	 *
	 * @param position
	 * @param offset
	 */

	float lasthx = 0;

	public void scroll(int position, float offset) {

		int tabWidth = childViewWidth;
		int tabVisible = getWidth() / tabWidth;
		float right = tabWidth - (getWidth() - tabVisible * tabWidth);

		mTranslationX = childViewWidth * (position + offset);

		if (getChildCount() * tabWidth - getWidth() > 0) {
			if (offset > 0.0001f && position + 1 >= tabVisible) {
				if (position + 2 >= getChildCount() && offset > 0.0001f) {
					int x = (int) (lasthx + right * offset);
					this.scrollTo(x, 0);
					horizontalScrollView.scrollTo(x, 0);
					horizontalScrollView.invalidate();
				} else {
					int x = (int) (tabWidth * (position + 1 + offset - tabVisible));
					this.scrollTo(x, 0);
					lasthx = x;
					horizontalScrollView.scrollTo(x, 0);
					horizontalScrollView.invalidate();
				}
			}

		}
		invalidate();

	}

	/**
	 * 获得屏幕的宽度
	 *
	 * @return
	 */
	public int getScreenWidth() {
		WindowManager wm = (WindowManager) getContext().getSystemService(
				Context.WINDOW_SERVICE);
		DisplayMetrics outMetrics = new DisplayMetrics();
		wm.getDefaultDisplay().getMetrics(outMetrics);
		return outMetrics.widthPixels;
	}

}

主要的重点是理解几个问题:初始化三角形并且移动使三角形移动的是用到Carvas

/**
	 * 初始化三角形指示器
	 */
	private void initTriangle() {
		mPath = new Path();

		mTriangleHeight = (int) (mTriangleWidth / 2 / Math.sqrt(2));
		mPath.moveTo(0, 0);
		mPath.lineTo(mTriangleWidth, 0);
		mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);
		mPath.close();
	}
/**
	 * 绘制指示器
	 */
	@Override
	protected void dispatchDraw(Canvas canvas) {
		canvas.save();
		// 画笔平移到正确的位置
		canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1);
		canvas.drawPath(mPath, mPaint);
		canvas.restore();

		super.dispatchDraw(canvas);
	}

而三角形一定的距离需要的参数就在ViewPager的移动监听OnPagerLisnear方法中,我写了个方法

/**
	 * 指示器跟随手指滚动,以及容器滚动
	 *
	 * @param position
	 * @param offset
	 */

	float lasthx = 0;

	public void scroll(int position, float offset) {

		int tabWidth = childViewWidth;
		int tabVisible = getWidth() / tabWidth;
		float right = tabWidth - (getWidth() - tabVisible * tabWidth);

		mTranslationX = childViewWidth * (position + offset);

		if (getChildCount() * tabWidth - getWidth() > 0) {
			if (offset > 0.0001f && position + 1 >= tabVisible) {
				if (position + 2 >= getChildCount() && offset > 0.0001f) {
					int x = (int) (lasthx + right * offset);
					this.scrollTo(x, 0);
					horizontalScrollView.scrollTo(x, 0);
					horizontalScrollView.invalidate();
				} else {
					int x = (int) (tabWidth * (position + 1 + offset - tabVisible));
					this.scrollTo(x, 0);
					lasthx = x;
					horizontalScrollView.scrollTo(x, 0);
					horizontalScrollView.invalidate();
				}
			}

		}
		invalidate();

	}

代码中有个要注意的就是但三角形移动超出屏幕时想要再显示的话,本身这个LinearLayout一定要也要移动,上面的HorizontalScrollView也跟着移动就能做到跟内容跟tab也跟着移动的效果。

我做出的效果跟其他的都有区别就是如果tab页会超出屏幕,我是在滑到接近屏幕的最后一个时,才控制内容,tab一起移动的。

再看MainActivity

package com.example.demotab;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {
	private List<Fragment> mTabContents = new ArrayList<Fragment>();
	private FragmentPagerAdapter mAdapter;
	private ViewPager mViewPager;
	private List<View> mDatas = new ArrayList<View>();
	private ViewPagerIndicator mIndicator;
	private HorizontalScrollView horizontalScrollView;
	private int tabWidth;
	private LinearLayout llLayout;
	private List<String> list = Arrays.asList("栏目一", "栏目二", "栏目三", "栏目四",
			"栏目五", "栏目六", "栏目七", "栏目八");

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mViewPager = (ViewPager) findViewById(R.id.vp);
		mIndicator = (ViewPagerIndicator) findViewById(R.id.indicator);
		llLayout = (LinearLayout) findViewById(R.id.ll1);
		horizontalScrollView = (HorizontalScrollView) findViewById(R.id.hsv);
		tabWidth = this.getResources().getDimensionPixelOffset(R.dimen.text_w);
		for (int i = 0; i < list.size(); i++) {
			mDatas.add(new View(this));
			TextView textView = new TextView(this);
			textView.setText(list.get(i));
			textView.setGravity(Gravity.CENTER);
			LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
					tabWidth, LinearLayout.LayoutParams.FILL_PARENT);
			llLayout.addView(textView,layoutParams);
			mTabContents.add(MainTab.newInstance(list.get(i)));
		}
		mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
			@Override
			public int getCount() {
				return mTabContents.size();
			}

			@Override
			public Fragment getItem(int position) {
				return mTabContents.get(position);
			}
		};

		mIndicator.setChildViewWidth(tabWidth);
		mIndicator.setHorizontalScrollView(horizontalScrollView);
		mIndicator.setTabItemTitles(mDatas);
		// 设置关联的ViewPager
		mViewPager.setAdapter(mAdapter);
		mViewPager.setCurrentItem(0);
		mIndicator.setViewPager(mViewPager, 0);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}

源码等下发出

时间: 2024-11-08 10:11:47

ViewPagerIndicator 仿网易新闻tab或是知乎的发现中的tab的相关文章

Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签

之前用JakeWharton的开源框架ActionBarSherlock和ViewPager实现了对网易新闻客户端Tab标签的功能,ActionBarSherlock是在3.0以下的机器支持ActionBar的功能,有兴趣的可以看看开源框架ActionBarSherlock 和 ViewPager 仿网易新闻客户端,今天用到的是JakeWharton的另一开源控件ViewPageIndicator,ViewPager想必大家都知道,Indicator指示器的意思,所以ViewPageIndica

Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻clientTab标签

之前用JakeWharton的开源框架ActionBarSherlock和ViewPager实现了对网易新闻clientTab标签的功能,ActionBarSherlock是在3.0下面的机器支持ActionBar的功能,有兴趣的能够看看开源框架ActionBarSherlock 和 ViewPager 仿网易新闻client,今天用到的是JakeWharton的还有一开源控件ViewPageIndicator.ViewPager想必大家都知道,Indicator指示器的意思,所以ViewPag

Android应用经典主界面框架之二:仿网易新闻client、CSDN client (Fragment ViewPager)

另外一种主界面风格则是以网易新闻.凤凰新闻以及新推出的新浪博客(阅读版)为代表.使用ViewPager+Fragment,即ViewPager里适配器里放的不是一般的View.而是Fragment.所以适配器不能继承PagerAdapter,而要继承FragmentPagerAdapter,这是在android.support.v4.app.FragmentPagerAdapter包里的.有点奇葩的是,FragmentPagerAdapter仅仅在这个包里有,在android.app.*这个包以

Android应用经典主界面框架之二:仿网易新闻客户端、CSDN 客户端 (Fragment ViewPager)

第二种主界面风格则是以网易新闻.凤凰新闻以及新推出的新浪博客(阅读版)为代表,使用ViewPager+Fragment,即ViewPager里适配器里放的不是一般的View,而是Fragment.所以适配器不能继承PagerAdapter,而要继承FragmentPagerAdapter,这是在android.support.v4.app.FragmentPagerAdapter包里的.有点奇葩的是,FragmentPagerAdapter只在这个包里有,在android.app.*这个包下面么

Android 开源框架ActionBarSherlock 和 ViewPager 仿网易新闻客户端

转载请注明出处:http://blog.csdn.net/xiaanming/article/details/9971721 大家都知道Android的ActionBar是在3.0以上才有的,那么在3.0以下呢,google并没有给我提供在3.0以下支持ActionBar的包,但是外国的大牛JakeWharton实现了在3.0以下使用ActionBar, JakeWharton这位大牛是ActionBarSherlock,Android-ViewPagerIndicator ,NineOldAn

关于仿网易新闻中详细页图文混排功能的实现

最近在了解关于新闻内容的图文混排的效果,网上有人开源一个仿网易新闻的代码,本文就是简单记录学习其详细页面显示的效果实现: 下载地址:https://github.com/dsxNiubility/SXNews 效果图: 其原理:通过网络请求获得相关的信息,再通过手机端进行拼HTML,然后在WebView进行展示,此处还对文章中的图片增加点击效果,可以保存到相册中:文章的样式已经存在项目中,直接去调用: 1:首先了解两个相关的实体对象,一个是新闻的主体内容,另外一个就是图片的相关信息实体: 1:主

iOS界面-仿网易新闻左侧抽屉式交互

1.介绍 用过网易新闻客户端的同学都会发现,网易新闻向左滑动时,左侧的导航栏会跟着拖动出来,新闻内容列表会拉到最右侧.像一个抽屉拉出来一样.很酷.除了网易新闻,现在好多应用都采用了这样的交互. 对手势识别不熟悉的请参考上篇: iOS手势识别的详细使用(拖动,缩放,旋转,点击,手势依赖,自定义手势) 这个交互效果主要用到两个手势,一个是pan拖拽,一个是tap点击.拖拽可以把抽屉拉出来,再推回去.点击可以把抽屉推回去. 效果如下:     那么这个效果如何实现呢? 2.实现思路和步骤 思路:从实现

android 仿网易新闻客户端源码都有

原文:android 仿网易新闻客户端源码都有 android 仿网易新闻服务端源码 源代码下载地址: http://www.zuidaima.com/share/1550463560944640.htm http://www.zuidaima.com/share/1550463561206784.htm android 仿网易新闻 客户端和服务端 源码都有 ,有些功能还未实现,因为文件有点大,所以分为2次上传  java源代码截图:

iOS界面-仿网易新闻左侧抽屉式交互 续(添加新闻内容页和评论页手势)

本文转载至  http://blog.csdn.net/totogo2010/article/details/8637430 1.介绍 有的博友看了上篇博文iOS界面-仿网易新闻左侧抽屉式交互 ,在微博里问,网易新闻里的内容和评论的拖拽如何实现, 上面的UINavigation如何嵌进去.可能不少人有这样的需求,现在花了些时间把这两个效果做一下, 和大家分享交流.思路和上篇基本差不多,但是没有用到UINavigation,其实在我看来上面的返回. 评论按钮都是可以通过addsubview添加的.