Android 自定义可滚动导航栏

先上效果图:
在这里插入图片描述
现在很多新闻类型的app,例如头条app都有图上的导航栏效果。看似一个简单的效果,实际上用到了很多知识点,例如动态创建布局,动态给控件设置宽高、滑动效果、滑动惯性效果、事件分发等。

其实用横屏的scrollview也能实现类似的效果,但是总觉得用现成的控件实现起来没多大意思,也锻炼不了开发能力。所有我们通过自定义的方式去实现,装逼效果可达到满分。
当我们设置宽度控件为wrap_content时,如果导航条宽度(tab有很多个)大于屏幕宽度,控件可通过手势移动实现滚动效果,快速滑动时也有惯性效果。当控件滚动时tab选项不可点击,静止时点击切换选项卡。如何设置控件宽度为match_parent或者小于match_parent的具体宽度时,tab选项卡宽度等比例分配。

简单实现原理:控件继承自RelativeLayout,子view是一个ImageView(为了实现tab切换动画效果)和LinearLayout(选项卡item的父布局)。切换选项卡是ImageView实现移动动画效果。我们把选显卡item都动态添加进LinearLayout里。

先看如何动态创建布局的:两种方式:一种是添加自定义布局,另一种是添加的是textview。

	/**
	    * 添加数据
	    * @param list
    */
	public void addItems(final List list){
	       if(list == null || list.size() == 0){
	           return;
	       }
	       itemsDate = list;
	       if(selectionListener!=null){
	           selectionListener.select(selectId);
	       }
	       setLayout();
	       requestLayout();
	   }
	   /**
	    * 添加item
	    */
    private void setLayout(){
        for (int i = 0 ; i<itemsDate.size() ; i++){
            if(adapter != null){
                ViewGroup view = (ViewGroup) adapter.createView();
                adapter.bind(itemsDate.get(i),view,i);
                LinearLayout wrapperLL = new LinearLayout(getContext());
                wrapperLL.addView(view);
                wrapperLL.setGravity(Gravity.CENTER);
                wrapperLL.setOnClickListener(this);
                wrapperLL.setTag(i);
                wrapperLL.setPadding(paddingLeft,paddingTop,paddingRight,paddingBottom);
                linearLayout.addView(wrapperLL);
            }else {
                textView = new TextView(getContext());
                String text = itemsDate.get(i)+"";
                textView.setText(text);
                textView.setGravity(Gravity.CENTER);
                if(i==0){
                    textView.setTextColor(Color.parseColor(selectTextViewColor));
                }else{
                    textView.setTextColor(Color.parseColor(noSelectTextViewColor));
                }
                textView.setOnClickListener(JNView.this);
                textView.setTag(i);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,textSize);
                textView.setPadding(paddingLeft,paddingTop,paddingRight,paddingBottom);
                linearLayout.addView(textView);
            }
        }
    }

自定义布局适配器:定义一个JNAdapter接口,实现了绑定view&createView(),绑定数据bind(),添加数据addItems()三个方法。

 public interface JNAdapter<T>{
        public  View createView();
        public  void bind(T t,View view,int position);
        public  List<T> addItems();
    }
    public void setAdater(JNAdapter adapter){
        this.adapter = adapter;
        addItems(adapter.addItems());
    }

创建适配器:

 for (int i=0;i<mDatasBackup.length;i++){
            Bean bean = new Bean();
            bean.text = mDatasBackup[i]+i;
            list.add(bean);
        }
       JNView.JNAdapter adapter = new JNView.JNAdapter<JNViewActivity.Bean>() {
           @Override
           public View createView() {
               return LayoutInflater.from(JNViewActivity.this).inflate(R.layout.jn_listitems_layout,null,false);
           }
           @Override
           public  void bind(JNViewActivity.Bean t, View view, int position) {
               TextView textView = (TextView) view.findViewById(R.id.tv);
               textView.setText(t.text);
           }
           @Override
           public  List<JNViewActivity.Bean> addItems() {
               return list;
           }
       };
       jnview.setAdater(adapter);
       
		public class Bean{
        	public String text;
        	public int drawableId;
    }

当控件宽度Mode不同时计算对应的宽度和选项卡宽度:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //固定高度。
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.getMode(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(itemsDate==null||itemsDate.size()==0){
            return;
        }
        int widthMode;
        widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if(widthMode==MeasureSpec.AT_MOST){
            width = getMaxItemWidth()*itemsDate.size();
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.getMode(widthMeasureSpec));
            animationWidth = width;
            for (int i = 0 ; i< linearLayout.getChildCount() ; i++){
                linearLayout.getChildAt(i).setLayoutParams(new LinearLayout.LayoutParams(maxItemWidth,LinearLayout.LayoutParams.MATCH_PARENT));
            }
            if(width>screenWidth){
                scrollFlag = true;
                dValue = width - screenWidth;
                width = screenWidth;
            }
            this.params.width = getMaxItemWidth();
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }else{
            width = getMeasuredWidth();
            animationWidth = width;
            params.width = width/itemsDate.size();
            params.height = height;
            paramsLinearLayout.height = height;
            paramsLinearLayout.width = width;
            for (int i = 0 ; i< linearLayout.getChildCount() ; i++){
                linearLayout.getChildAt(i).setLayoutParams(new LinearLayout.LayoutParams(width/itemsDate.size(),LinearLayout.LayoutParams.MATCH_PARENT));
            }
        }
        setMeasuredDimension(width,height);
    }

当我们Mode为MeasureSpec.AT_MOST时,LinearLayout的宽度为tab中最大宽度乘以tab的个数;并设置每个tab宽度为maxItemWidth。

/**
     * 当widthMode==MeasureSpec.AT_MOST时得到item的最大宽度
     * @return
     */
    public int getMaxItemWidth(){
        int[] widthItems = new int[linearLayout.getChildCount()];
        for (int i = 0 ; i< linearLayout.getChildCount() ; i++){
            View view = linearLayout.getChildAt(i);
            int width = view.getMeasuredWidth()+view.getPaddingLeft()+view.getPaddingRight();
            widthItems[i] = width;
        }
        for (int i = 0 ; i< widthItems.length-1; i++){
            if(widthItems[i]>widthItems[i+1]){
                maxItemWidth = widthItems[i];
                widthItems[i+1] = maxItemWidth;
            }else{
                maxItemWidth = widthItems[i+1];
            }
        }
        return maxItemWidth;
    }

高度写死:

 heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.getMode(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

当LinearLayout宽度大于他的父布局宽度时就需要有滑动效果了:
首先当LinearLayout宽度大于他的父布局宽度时scrollFlag为true,调用onInterceptTouchEvent拦截子事件所有事件。通过父布局也就是RelativeLayout的touch事件获取移动距离让LinearLayout滑动。

 @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return scrollFlag;
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(scrollFlag){
            boolean eventFlag = gd.onTouchEvent(event);
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    viewGroup = (ViewGroup) v;
                    break;
                case MotionEvent.ACTION_MOVE:
                    float offsetX = event.getX()-downX;
                    downX = event.getX();
              
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值