先上效果图:
现在很多新闻类型的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
自定义布局适配器:定义一个JNAdapter接口,实现了绑定view&createView(),绑定数据bind(),添加数据addItems()三个方法。
public interface JNAdapter{
public View createView();
public void bind(T t,View view,int position);
public List addItems();
}
public void setAdater(JNAdapter adapter){
this.adapter = adapter;
addItems(adapter.addItems());
}
创建适配器:
for (int i=0;i() {
@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 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();
int llLeft =