今天带来的是仿今日头条首页的联动滑动效果,废话不多说,先上效果图:
思路:
做这个我们需要实现的效果有
1、滑动内容区域,标题栏会有变化来显示当前所处的位置。
2、点击标题栏,内容区域也会随着滑动并跳到指定栏,标题栏会移动到屏幕中间。
3、当标题栏过多时,我们可以滑动标题栏找到超出屏幕的栏。
要实现上面三个问题,我采用的是标题栏用HorizontalScrollView
来实现,内容区域用ViewPager
来实现,力求达到最简单的实现方式。
首先HorizontalScrollView
能够水平方向拖动,只要在里面包含一个水平方向的LinearLayout,然后LinearLayout里面包含TextView,然后把HorizontalScrollView的HorizontalScrollBarEnabled设置为false,用于隐藏它原本的水平方向的滚动条,这样就可以实现我们所说的第三点了。
再来看下第一点,要想标题栏随着ViewPager变化,也就是我们得知道ViewPager当前所处的位置,这样自然我们就想到了ViewPager的OnPageChangeListener方法:
@Override
public void onPageSelected(int position) {
}
其中的参数position为我们提供这个位置信息。为了让标题栏中的TextView和ViewPager中Fragement一一对应,由于TextView本身没有这个属性,所有我们可以通过重写的方式给他添加一个index位置信息。
现在主要的是第二个问题,这里涉及到两个滑动,ViewPager的滑动是自带的我们可以不用管,而HorizontalScrollView本身是可以通过手动来滑动的,但是怎么让它自动滑到我们想要的地方呢。
还好HorizontalScrollView提供了一个smoothScrollTo(int x, int y)方法,使用这个方法,我们可以将HorizontalScrollView滑动到任意位置。
问题使我们怎么确定这个位置,由于每个TextView里面的文字数目可能不同,意味着TextView的宽度各不相同,这样要怎么计算位置呢?
我们可以使用getLeft()方法获得目标TextView距离HorizontalScrollView最左边的长度,这样就不用管之前的TextView的宽度了,因为getLeft()相当于获得了它们的和,我们希望它移动到中间位置,那么getLeft()再减去(getWidth()-TextView.getWidth())/2就是需要滑动的距离,具体逻辑看图:
图中黑框是屏幕,红色矩形是Textview,这里要将Textview从A位置移动到B位置,其中的蓝色线条就是移动的距离TextView.getLeft() - (getWidth() - TextView.getWidth()) / 2。getWidth()获取的是屏幕宽度。
最后调用smoothScrollTo(int x, int y)方法就ok了,x传上面得到的值,y为0就行。
最后给TextView加上属性动画效果就好了,对于属性动画ObjectAnimator
不太了解的自己去百度下吧,这里就不介绍了。
既然问题都解决了下面贴上主要代码:
public class MyIndicator extends HorizontalScrollView implements ViewPager.OnPageChangeListener{
private ViewPager mViewPager;
private LinearLayout myLinearLayout;
/**
我们实现了ViewPager.OnPageChangeListener接口,因为我们要监听ViewPager的滑页行为,从而去改变导航栏的状态
所以我们也可以看到,MyIndicator持有ViewPager的引用,如果我们还想要监听ViewPager怎么办呢?
这里我为MyIndicator提供了一个接口,与OnPageChangeListener方法一样调用 **/
MyOnPageChangeListener mListener;
private interface MyOnPageChangeListener {
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
void onPageSelected(int position);
void onPageScrollStateChanged(int state);
}
private int oldSelected;
//为了让TextView能点击,我为每个TextView都设置了OnClickListener,
//就是获得目标标题栏的index,然后调用setCurrentItem()就可以了,这样就实现了点击滑动的效果,点击标题栏,Viewpager也会跟着翻页。
private final OnClickListener mTabClickListener = new OnClickListener() {
public void onClick(View view) {
MyTabView tabView = (MyTabView)view;
oldSelected = mViewPager.getCurrentItem();
final int newSelected = tabView.index;
setCurrentItem(newSelected);
}
};
public MyIndicator(Context context) {
super(context);
init(context);
}
public MyIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context mcontext){
setHorizontalScrollBarEnabled(false);//隐藏自带的滚动条
//添加linearLayout
myLinearLayout = new LinearLayout(mcontext);
myLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
addView(myLinearLayout, new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
}
/**
* 我们必须在调用setViewPager()之前为ViewPager设置Adapter,
而FragmentPagerAdapter里面的getCount()方法返回了这个数目,
如果没有设置Adapter,MyIndicator就不知道怎么绘制导航栏了,因为连标题数目都不清楚 */
public void setViewPager(ViewPager viewPager){
setViewPager(viewPager,0);
}
@SuppressWarnings("deprecation")
public void setViewPager(ViewPager viewPager,int initPos){
if (mViewPager == viewPager) {
return;
}
if (mViewPager != null) {
mViewPager.setOnPageChangeListener(null);
}
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
mViewPager = viewPager;
viewPager.setOnPageChangeListener(this);
notifyDataSetChanged();
setCurrentItem(initPos);
}
private void notifyDataSetChanged(){
myLinearLayout.removeAllViews();
PagerAdapter mAdapter = mViewPager.getAdapter();
int count = mAdapter.getCount();
for(int i=0;i<count;i++){
addTab(i,mAdapter.getPageTitle(i));
}
requestLayout();
}
/**
* 代码添加顶部的TextView
* @param index
* @param text
*/
private void addTab(int index,CharSequence text) {
MyTabView tabView = new MyTabView(getContext());
tabView.index = index;
tabView.setFocusable(true);
tabView.setOnClickListener(mTabClickListener);
tabView.setText(text);
tabView.setTextSize(22);
tabView.setTextColor(getContext().getResources().getColor(R.color.white));
tabView.setPadding(20,0,20,0);
myLinearLayout.addView(tabView);
}
/**
* 被选中的动画
* @param view
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressLint("NewApi")
private void animation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f);
ObjectAnimator fade = ObjectAnimator.ofFloat(view, "alpha", 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(fade);
animSet.setDuration(500);
animSet.start();
}
/**
* 没选中的动画
* @param view
*/
private void animation2(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 0.8f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 0.8f);
ObjectAnimator fade = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(fade);
animSet.setDuration(500);
animSet.start();
}
public void setCurrentItem(int item) {
if (mViewPager == null) {
throw new IllegalStateException("ViewPager has not been bound.");
}
int mSelectedTabIndex = item;
mViewPager.setCurrentItem(item);
final int tabCount = myLinearLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {//遍历标题,改变选中的背景
final View child = myLinearLayout.getChildAt(i);
final boolean isSelected = (i == item);
child.setSelected(isSelected);
if (isSelected) {
animation(child);
animateToTab(item);//动画效果
}else{
animation2(child);
child.setBackgroundColor(Color.TRANSPARENT);
}
}
}
private Runnable mTabSelector;
private void animateToTab(final int position) {
final View tabView = myLinearLayout.getChildAt(position);
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
}
mTabSelector = new Runnable() {
public void run() {
final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;//计算要滑动到的位置
smoothScrollTo(scrollPos, 0);
mTabSelector = null;
}
};
post(mTabSelector); //在主线程执行动画
}
public void setMyOnPageChangeListener(MyOnPageChangeListener listener){
mListener = listener;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(mListener!=null) mListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
setCurrentItem(position);
if(mListener!=null) mListener.onPageSelected(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if(mListener!=null) mListener.onPageScrollStateChanged(state);
}
}
因为里面注释都写的差不多了,这里就不再细说了。
下面是布局xml文件:
<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:orientation="vertical"
tools:context="com.zhu.myindicator.MainActivity" >
<com.zhu.myindicator.MyIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/red" />
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
最后在Activity里面调用:
public class MainActivity extends FragmentActivity {
private MyIndicator indicator;
private ViewPager pager;
String content[]={"热点","推荐","社会","视频","科技","汽车","趣图", "美图", "美女"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentPagerAdapter adapter = new MyFragmentManager(getSupportFragmentManager(), content);
pager = (ViewPager)findViewById(R.id.pager);
indicator = (MyIndicator)findViewById(R.id.indicator);
pager.setAdapter(adapter);
indicator.setViewPager(pager);
}
}
最后还有些文件没有贴出来,大家可以下载demo来看:Android 仿今日头条首页标题栏效果