模仿微信,android bottom navigation的实现

    xiaoxiao2021-03-25  25

    微信出现之后,它的底部导航(bottom navigation)引领了潮流,后来,很多软件都进行模仿,纷纷加入了bottom navigation。那么,我也来模仿模仿,虽然与微信的底部效果有点区别,但是,毕竟是个学习的过程,就记录了下来,也给大家参考参考。

    先给大家看一下效果:

                                                                                 

    下面介绍一下实现过程:

    ItemView的设计

    BottomNavigation有四个ItemView,对应四个TabItemView,TabItemView布局也很简单,如下(tab_item_view.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"> <ImageView android:id="@+id/tab_icon" android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="center_horizontal" /> <TextView android:id="@+id/tab_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="12sp" /> </LinearLayout>定义了一个ImageView和一个TextView。tab_item_view.xml这个布局文件将会在TabItemView这个类中引用。 TabItemView继承了LinearLayout, public class TabItemView extends LinearLayout{ private ColorStateList colorStateList; private ImageView mTabIcon; private TextView mTabText; public TabItemView(Context context) { this(context, null); } public TabItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TabItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } public void setDefaultTextColor(ColorStateList colorStateList) { this.colorStateList = colorStateList; } private void initView(Context context) { setOrientation(VERTICAL); setGravity(Gravity.CENTER); LayoutInflater.from(context).inflate(R.layout.tab_item_view, this, true); mTabIcon = (ImageView) findViewById(R.id.tab_icon); mTabText = (TextView) findViewById(R.id.tab_text); } public void initData(TabItem tabItem) { if (mTabIcon != null && mTabText!= null && colorStateList != null){ mTabIcon.setImageResource(tabItem.getImageResId()); mTabText.setTextColor(colorStateList); mTabText.setText(tabItem.getTextResId()); } } }在initView方法中获取到布局并获取tabIcon和tabText,initData方法用来初始化tabIcon和tabText,colorStateList是一个ColorStateList对象,这个对象在setDefaultTextColor中被初始化,它定义了selected=true和selected=false两个状态的TextView的颜色。 以上介绍的是BottomNavigation中每个ItemView的创建,接下来定义一个Layout,把这些ItemView放置到其中:

    TabLayout的创建:

    创建TabLayout继承自LinearLayout,并实现View.OnClickListener接口: public class TabLayout extends LinearLayout implements View.OnClickListener { private List<TabItem> tabs; private View selectedView; private int tabCount; private ColorStateList colorStateList; private ViewPager mViewPager; public TabLayout(Context context) { this(context, null); } public TabLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { setOrientation(HORIZONTAL); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout); for (int i = 0; i < a.getIndexCount(); i++) { int attr = a.getIndex(i); if (attr == R.styleable.TabLayout_textColorSelector) { colorStateList = a.getColorStateList(attr); } } a.recycle(); } @Override public void onClick(View v) { tabClickListener.onTabClick((TabItem) v.getTag()); } public void intData(List<TabItem> tabItems, OnTabClickListener listener) { this.tabClickListener = listener; this.tabs = tabItems; LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT); params.weight = 1; params.setMargins(12,9,12,10); TabItemView itemView = null; if (tabs != null && tabs.size() > 0) { tabCount = tabs.size(); for (int index = 0; index < tabs.size(); index++) { itemView = new TabItemView(getContext()); itemView.setDefaultTextColor(colorStateList); itemView.setTag(tabs.get(index)); itemView.initData(tabs.get(index)); itemView.setOnClickListener(this); addView(itemView, params); } } else { throw new IllegalArgumentException("tabs can not be empty"); } } public void setCurrentTab(int position) { if (position >= 0 && position < tabCount) { View view = getChildAt(position); if (selectedView != view) { view.setSelected(true); if (selectedView != null) { selectedView.setSelected(false); } selectedView = view; } } } public void setTextColorSelected(ColorStateList colorStateList){ this.colorStateList = colorStateList; } public interface OnTabClickListener { void onTabClick(TabItem tabItem); } OnTabClickListener tabClickListener; }在initView方法中获取属性,这个属性在attrs.xml文件中定义,将会在布局文件使用到这个属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="TabLayout"> <attr name="textColorSelector" format="reference"/> </declare-styleable> </resources>在initData()方法中,利用一个循环,初始化所有的TabItemView: for (int index = 0; index < tabs.size(); index++) { itemView = new TabItemView(getContext()); itemView.setDefaultTextColor(colorStateList); //设置默认的colorStateList itemView.setTag(tabs.get(index)); //设置每个itemView的Tag,便于点击时,利用getTag方法来获取点击的ItemView itemView.initData(tabs.get(index)); //调用TabItemView中的initData方法来初始化每个ItemView itemView.setOnClickListener(this); //初始化监听 addView(itemView, params); //添加View }setCurrentTab()方法用来切换每个TabItemView的selected状态。 定义一个接口OnTabClickListener,返回被点击的TabItemView 到此,一个BottomNavigation已经完成一半了,接下来就是,怎么配合ViewPager来进行页面切换了。 不知道大家有没有遇到这种情况: 如果使用官方提供的ViewPager,当当前页面是第1个页面,然后点击切换到第3个页面的时候,第2个页面还是会被快速显示出来,然后在切换第3个页面。只要所点击的页面和当前页面之间还有其他页面,在点击切换时,都是把中间的所有页面快速切换而过。我不知道我这样描述大家有没有看懂,如果不懂,那我也只能抱歉了。接下来解决这个问题。

    自定义ViewPager

    解决的思路:当用户滑动页面进行页面切换时,允许ViewPager滑动。如果用户是通过点击底部的Tab来进行页面切换时,禁止ViewPager滑动,直接切换到点击所指的页面。 创建一个IndexViewPager继承自ViewPager public class IndexViewPager extends ViewPager { private boolean isCanScroll = true; public IndexViewPager(Context context) { super(context); } public IndexViewPager(Context context, AttributeSet attrs) { super(context, attrs); } public void setScanScroll(boolean isCanScroll) { this.isCanScroll = isCanScroll; } @Override public void scrollTo(int x, int y) { super.scrollTo(x, y); } @Override public boolean onTouchEvent(MotionEvent arg0) { // TODO Auto-generated method stub if (isCanScroll) { return super.onTouchEvent(arg0); } else { return false; } } @Override public void setCurrentItem(int item) { // TODO Auto-generated method stub super.setCurrentItem(item, false); } @Override public void setCurrentItem(int item, boolean smoothScroll) { // TODO Auto-generated method stub super.setCurrentItem(item, smoothScroll); } @Override public boolean onInterceptTouchEvent(MotionEvent arg0) { // TODO Auto-generated method stub if (isCanScroll) { return super.onInterceptTouchEvent(arg0); } else { return false; } } }定义一个变量isCanScroll来控制ViewPager是否可以滑动。在onTouchEvent和onInterceptTouchEvent两个方法中添加对isCanScroll的判断,选择性返回。其他两个方法都是默认的。在控制切换ViewPager页面的时候,调用setCanScroll方法就能解决以上所描述的问题了。对于我所提的这个问题,大家可以自行做下实验,增加理解。

    实现BottomNavigation:

    这里先定义一个Model的来保存每个TabItemView的数据, public class TabItem { /** * icon */ private int imageResId; /** * the text */ private int textResId; private Class<? extends Fragment> tagFramgentClz; public TabItem(int imageResId, int lableResId) { this.imageResId = imageResId; this.textResId = lableResId; } public TabItem(int imageResId, int lableResId, Class<? extends Fragment> tagFramgentClz) { this.imageResId = imageResId; this.textResId = lableResId; this.tagFramgentClz = tagFramgentClz; } public int getImageResId() { return imageResId; } public void setImageResId(int imageResId) { this.imageResId = imageResId; } public int getTextResId() { return textResId; } public void setTextResId(int textResId) { this.textResId = textResId; } public Class<? extends Fragment> getTagFramgentClz() { return tagFramgentClz; } public void setTagFramgentClz(Class<? extends Fragment> tagFramgentClz) { this.tagFramgentClz = tagFramgentClz; } } 在Activity的布局文件中添加IndexViewPager和TabLayout: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation='vertical' tools:context="com.wujs.bottomnavigationtest.MainActivity"> <com.wujs.bottomnavigation.IndexViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/tab_layout" /> <com.wujs.bottomnavigation.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="56dp" android:background="@color/white" android:layout_alignParentBottom="true" app:textColorSelector="@drawable/selector_tab_text" /> </RelativeLayout>在TabLayout中,定义textColorSelector属性,引用了一个selector资源文件(selector_tab_text.xml): <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/colorPrimary" android:state_selected="true" /> <item android:color="@color/darker_gray" android:state_selected="false" /> </selector> 在Activity中使用: public class MainActivity extends AppCompatActivity implements TabLayout.OnTabClickListener { @BindView(R.id.tab_layout) TabLayout tabLayout; @BindView(R.id.viewPager) IndexViewPager mViewPager; private List<TabItem> tabs; private int selectedPosition; private boolean isPermsGranted = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); initData(); } private void initData() { tabs = new ArrayList<>(); tabs.add(BottomTabPosConstant.HOME_TAB_POS, new TabItem(R.drawable.selector_tab_home_icon, R.string.tabactivity_tab_home_text, PaxFragment.class)); tabs.add(BottomTabPosConstant.CONCERN_TAB_POS, new TabItem(R.drawable.selector_tab_concern_icon, R.string.tabactivity_tab_concern_text, ConcernFragment.class)); tabs.add(BottomTabPosConstant.NEARBY_TAB_POS, new TabItem(R.drawable.selector_tab_nearby_icon, R.string.tabactivty_tab_nearby_text, NearbyFragment.class)); tabs.add(BottomTabPosConstant.ME_TAB_POS, new TabItem(R.drawable.selector_tab_me_icon, R.string.tabactivity_tab_me_text, MeFragment.class)); tabLayout.intData(tabs, this); tabLayout.setCurrentTab(0); FragAdapter adapter = new FragAdapter(getSupportFragmentManager()); mViewPager.setAdapter(adapter); mViewPager.setOffscreenPageLimit(2); //设置限定预加载的ViewPager页面个数 //Change the selected tag, when the viewpager changes page. mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { mViewPager.setScanScroll(true); //enable scroll of viewpager tabLayout.setCurrentTab(position); selectedPosition = position; } @Override public void onPageScrollStateChanged(int state) { } }); } @Override public void onTabClick(TabItem tabItem) { int position = tabs.indexOf(tabItem); if (position != selectedPosition) { mViewPager.setScanScroll(false); //disable scroll of viewpager selectedPosition = position; mViewPager.setCurrentItem(tabs.indexOf(tabItem)); // Here change the selected tag by setting current item of ViewPager. } } private class FragAdapter extends FragmentPagerAdapter { FragAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int arg0) { try { return tabs.get(arg0).getTagFramgentClz().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } } @Override public int getCount() { return tabs.size(); } } } 到此,一个模仿微信的BottomNavigation就完成了,欢迎大家来吐槽。

    代码已经上传到上:点击下载BottomNavigationTest

    也可以到github上去下载:github

    转载请注明原文地址: https://ju.6miu.com/read-150115.html

    最新回复(0)