300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Android 高仿华为手机Tab页滑动导航效果

Android 高仿华为手机Tab页滑动导航效果

时间:2022-10-10 04:25:19

相关推荐

Android 高仿华为手机Tab页滑动导航效果

首先带大家看一下实现效果,用了两种实现方式:

1.基于LinearLayout实现,导航栏不可响应手指滑动

2.基于HorizontalScrollView实现,导航栏可响应手指滑动

实现方式虽然不一样,但是使用的是一样的,因为我接口封装的一模一样,下面看实现效果。

基于LinearLayout的实现:

基于HorizontalScrollView的实现:

两者效果一样,区别就在于导航条可否随用户操作滑动。

下面只说明LinearLayout实现,HorizontalScrollView仅仅是套了一层LinearLayout,因为它只允许套一个子控件。

需求开发点:

1.自定义控件的编写(通用方法:构造函数、init()、onMeasure、onLayout、onDraw、dispatchDraw)

2.白色圆点的偏移量的计算

3.当Tab页超过5个的时候,白点和LinearLayout内容的组合滑动,达到白点不动,而文字移动的效果。

实现这三点,这个控件就算完事,当然一些细节方面的,可以根据自己的需求去调优。

1.自定义控件的编写

1) 这里主要是onMeasure方法的复写,不能用默认的onMeasure,需要我们根据Tab的数量去计算,下图是1-6个Tab的measure结果

这个就是根据Tab的数量平分屏幕的宽,当数量超过5个的时候,为了保持效果,只显示5个,超出的内容将其追加到屏幕外面,屏幕是有界的,视图是无界的,你可以无限往右边添加,只要内存能容下,可以用个for循环无限添加看看发生什么。

2) 复写dispatchDraw方法,这里dispatchDraw方法的作用就是画一个白色的圆点。

白色圆点的偏移量的计算

这个得靠ViewPager的OnPageChangeListener,通过它onPageScrolled的回调去计算,计算好了调用invalidate()去重绘。

当Tab页超过5个的时候,白点和LinearLayout内容的组合滑动,达到白点不动,而文字移动的效果。

当滑动到第5个Tab,如果只是白色圆点移动就会移动到屏幕外面去,这个时候也需要让LinearLayout内容联动,通过scrollTo方法,往与白点相反的方向移动内容,使其和白点的移动达到平衡,这样白点不动,上面的标题内容移动。

下面分析,写该控件需要的一些变量,先看截图:

1.需要白点是吧,我们用画笔画,所以Paint需要

2.白点画多大?所以需要一个白点的半径变量radius

3.白点的偏移量,我们定义一个变量mOffset,根据ViewPager的滑动动态计算

4.上面还有一排文字,用TextView实现,这个TextView是根据Tab的数量动态添加的,数量不可控,我这里并没有定义成变量。但是文字内容对应的是一个Fragment的标题,也就是说标题和Fragment是一一对应的关系。OK用Map。

/*** 存放Tab的集合 Key:导航栏的标题 Fragment:导航页*/private Map<String, Fragment> mTabs;

TextView就是根据mTabs的数量动态添加的。

5.我们怎么去计算滑动偏移量呢?这个得靠ViewPager的OnPageChangeListener,通过它onPageScrolled的回调去计算mOffset,就是通过这种计算圆点的偏移,这里我持有了一个ViewPager的引用,所以有变量mViewPager,其实要不要关系不大.

6.屏幕最大可显示的Tab数量MAX_VISIBLE_TAB_COUNTS,总不能有100个你就显示100个吧.

7.实际屏幕上可显示的Tab数量mRealVisibleTabCounts

8.屏幕宽度mScreenWidth辅助onMeasure计算的

下面是实现代码:

package com.csm.hwtab;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Cap;import android.graphics.Paint.Style;import android.support.v4.app.Fragment;import android.support.v4.view.ViewPager;import android.support.v4.view.ViewPager.OnPageChangeListener;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.TypedValue;import android.view.Gravity;import android.view.View;import android.view.View.OnClickListener;import android.widget.LinearLayout;import android.widget.TextView;/*** @author Rander.C*/@SuppressLint("NewApi")public class TabLinearLayout extends LinearLayout implements OnClickListener, OnPageChangeListener {/*** 最大可显示的Tab数,可写成自定义属性在xml里面配置*/private static final int MAX_VISIBLE_TAB_COUNTS = 5;/*** 实际可见的Tab数,比如有8个Tab,超过8个,为了保证效果,最多显示5个* 此时mRealVisibleTabCounts为5* 如果只有3个tab小于5个,则mRealVisibleTabCounts为3* 并根据这个去计算每个tab的宽度,屏幕宽度除以mRealVisibleTabCounts嘛*/private int mRealVisibleTabCounts;/*** 小圆点滑动偏移量*/private float mOffset;/*** 绘制小圆点画笔*/private Paint mPaint;/*** 小圆点半径*/private int mCircleRadius;/*** 屏幕宽度*/private int mScreenWidth;/*** 存放Tab的集合 Key:导航栏的标题 Fragment:导航页*/private Map<String, Fragment> mTabs;/*** View中有getContext()方法,用变量就会* 多一个Context引用,会增加一丁点虚拟机的负担吗?* 如果不用变量,但是每次都使用getContext(),是不是都要多一层* 调用栈,这个怎么权衡,求答案,还是我想多了?*/private Context mContext;private ViewPager mViewPager;public TabLinearLayout(Context context, AttributeSet attrs) {this(context, attrs, -1);}public TabLinearLayout(Context context) {this(context, null);}public TabLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mContext = getContext();setOrientation(HORIZONTAL);mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setDither(true);mPaint.setStyle(Style.FILL);mPaint.setColor(Color.WHITE);mPaint.setStrokeCap(Cap.ROUND);DisplayMetrics metrics = getResources().getDisplayMetrics();mScreenWidth = metrics.widthPixels;mCircleRadius = (int) getResources().getDimension(R.dimen.radius);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int tabCount = getChildCount();if (tabCount == 0) {return;}/** 不管默认测量结果怎么样,我们都要重新给其宽度赋值*/mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < tabCount ? MAX_VISIBLE_TAB_COUNTS : tabCount;// 为每一个子view重新分配mScreenWidth / mRealVisibleTabCounts大小的宽度int averageWidth = mScreenWidth / mRealVisibleTabCounts;for (int i = 0; i < tabCount; i++) {View view = getChildAt(i);LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) view.getLayoutParams();params.weight = 0;params.width = averageWidth;view.setLayoutParams(params);}}/*** 获得Tabs列表* @return*/public List<Fragment> getTabs() {return new ArrayList(mTabs.values());}/*** 添加一个Tab项,添加一个tab的时候同时给* 该视图添加一个TextView* @param title* @param tab*/public void addTab(String title, Fragment tab) {if (mTabs == null) {mTabs = new LinkedHashMap<>();}mTabs.put(title, tab);addView(createTabItem(title, mTabs.size() - 1));}/*** 设置ViewPager* 同时给其设置OnPageChangeListener监听器。* @param viewPager*/public void setViewPager(ViewPager viewPager) {mViewPager = viewPager;mViewPager.setOnPageChangeListener(this);}/*** 创建一个Tab视图* @param titleFragment对应的标题* @param index该Fragment对应的索引下标* @return 已经构造好的tab视图*/private View createTabItem(String title, int index) {TextView tv_title = new TextView(mContext);tv_title.setText(title);tv_title.setTag(index);tv_title.setGravity(Gravity.CENTER);tv_title.setTextColor(Color.WHITE);tv_title.setTextSize(PLEX_UNIT_SP, 18);tv_title.setOnClickListener(this);return tv_title;}@Overridepublic void onClick(View v) {int tabIndex = (Integer) v.getTag();if (onTabOperatorListener != null) {onTabOperatorListener.onTabClick(tabIndex);}mViewPager.setCurrentItem(tabIndex);}@Overridepublic void onPageScrollStateChanged(int state) {if (null != onTabOperatorListener) {onTabOperatorListener.onPageScrollStateChanged(state);}}@Overridepublic void onPageScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels) {mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < getChildCount() ? MAX_VISIBLE_TAB_COUNTS : getChildCount();int tabWidth = getWidth() / mRealVisibleTabCounts;// 计算小圆点的偏移量,这个可以画图理解mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );// 如果滚动到最后一个,则同时要向左滚动tab内容,这种情况是:原点在向右移动,同时整体又向做滑动,两个滑动相互抵消,相当于原点没有移动if (getChildCount() > mRealVisibleTabCounts && positionOffset > 0&& sourcePosition >= mRealVisibleTabCounts - 1) {scrollTo((int) ((sourcePosition + 1 - mRealVisibleTabCounts) * tabWidth + tabWidth * positionOffset), 0);}invalidate();if (null != onTabOperatorListener) {onTabOperatorListener.onTabScrolled(sourcePosition, positionOffset, positionOffsetPixels);}}@Overridepublic void onPageSelected(int index) {if (null != onTabOperatorListener) {onTabOperatorListener.onTabSelected(index);}/*** 这里不要多想,只是为了保证选中的tab为第一个时,让布局重置到原点*/if(index == 0){scrollTo(0, 0);}}/*** Tab操作相关的监听器* @author rander*/public interface OnTabOperatorListener {void onTabClick(int tabIndex);void onTabSelected(int tabIndex);void onTabScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels);void onPageScrollStateChanged(int state);}public OnTabOperatorListener onTabOperatorListener;public void setOnTabItemClickListener(OnTabOperatorListener onTabOperatorListener) {this.onTabOperatorListener = onTabOperatorListener;}@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);//如果只有一个Tab则,不绘制圆点if (getChildCount() <= 1) {return;}//根据mOffset绘制偏移量canvas.drawCircle(mOffset, getHeight() - mCircleRadius * 2, mCircleRadius, mPaint);}}

关于偏移量的计算画图理解:

偏移量计算的表达式:

mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );

图解:

第一个竖线和第二个竖线:tabWidth / 2

第二个竖线和第三个竖线:sourcePosition * tabWidth

第三个竖线和第四个竖线:positionOffset * tabWidth

一相加就是偏移量了,这个要找到sourcePosition和positionOffset的规律就好计算偏移量了,scrollTo的偏移量也可以参照这个理解。

接下来就是使用了.求Linux下的好的画图工具

书写布局tab.xml:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><com.csm.hwtab.TabLinearLayout android:id="@+id/tab"android:layout_width="match_parent"android:layout_height="65dp"android:background="#0000ff"android:gravity="center_vertical"android:orientation="vertical" ></com.csm.hwtab.TabLinearLayout><android.support.v4.view.ViewPager android:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /></LinearLayout>

在Activity中使用:

package com.csm.hwtab;import com.csm.hwtab.adapter.MyFragmentPagerAdapter;import com.csm.hwtab.fragment.OneFragment;import android.os.Bundle;import android.support.v4.app.FragmentActivity;import android.support.v4.view.ViewPager;import android.view.Window;public class TestActivity extends FragmentActivity{private ViewPager mViewPager;private TabLinearLayout mTabview;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.tab);initTabsView();}private void initTabsView() {mViewPager = (ViewPager) findViewById(R.id.view_pager);mTabview = (TabLinearLayout)findViewById(R.id.tab);mTabview.addTab("先秦0", new OneFragment());mTabview.addTab("先秦1", new OneFragment());mTabview.addTab("先秦2", new OneFragment());mTabview.addTab("先秦3", new OneFragment());mTabview.addTab("先秦4", new OneFragment());mTabview.addTab("先秦5", new OneFragment());mTabview.addTab("先秦6", new OneFragment());mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), mTabview.getTabs()));mTabview.setViewPager(mViewPager);mViewPager.setCurrentItem(0);}}

效果就如同上面的效果一样了。

源码下载

github:/shuangmin/HwTab

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。