何为视图的顶部悬停呢?上图看吧:
我这里给出2中实现方式:
一,用ScrollView+listView实现。
问题的思考:
a,如何知道ScrollView滑动到了什么位置(即我怎么知道我的Y轴滑动到了哪里?)
我在网上发现了这么一个类ObservableScrollView,我不知道出处了,不好意思哈。他是重写ScrollView,在ScrollView的onScrollChange设置了一个接口回调,把滑动的那个Y轴通过接口传出去。
public class ObservableScrollView extends ScrollView {
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
super.onScrollChanged(l, t, oldl, oldt);
if (mCallbacks != null) {
mCallbacks.onScrollChanged(t);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
if (mCallbacks != null) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mCallbacks.onDownMotionEvent();
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
mCallbacks.onUpOrCancelMotionEvent();
break;
}
}
return super.onTouchEvent(ev);
}
@Override
protected int computeVerticalScrollRange() {
// TODO Auto-generated method stub
return super.computeVerticalScrollRange();
}
public static interface Callbacks {
public void onScrollChanged(int scrollY);
public void onDownMotionEvent();
public void onUpOrCancelMotionEvent();
}
private Callbacks mCallbacks;
public void setCallbacks(Callbacks listener) {
mCallbacks = listener;
}
}
b,悬停的动作怎么实现
悬停的动作当然是通过平移动画实现啦。
@Override
public void onScrollChanged(int scrollY) {
stickyView.setTranslationY(Math.max(stopView.getTop() - titleViewHeight, scrollY));
titleView.setBackgroundColor(ColorUtil.getNewColorByStartEndColor(this, (float)((scrollY * 1.0 / (stopView.getTop())) > 1 ? 1 : (scrollY * 1.0 / (stopView.getTop()))), R.color.colorTransparent, R.color.colorWhite));
}
c,标题栏颜色渐变的怎么实现
// 成新的颜色值
public static int getNewColorByStartEndColor(Context context, float fraction, int startValue, int endValue) {
return evaluate(fraction, context.getResources().getColor(startValue), context.getResources().getColor(endValue));
}
/**
* 成新的颜色值
* @param fraction 颜色取值的级别 (0.0f ~ 1.0f)
* @param startValue 开始显示的颜色
* @param endValue 结束显示的颜色
* @return 返回生成新的颜色值
*/
public static int evaluate(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff;
int startR = (startValue >> 16) & 0xff;
int startG = (startValue >> 8) & 0xff;
int startB = startValue & 0xff;
int endA = (endValue >> 24) & 0xff;
int endR = (endValue >> 16) & 0xff;
int endG = (endValue >> 8) & 0xff;
int endB = endValue & 0xff;
return ((startA + (int) (fraction * (endA - startA))) << 24) |
((startR + (int) (fraction * (endR - startR))) << 16) |
((startG + (int) (fraction * (endG - startG))) << 8) |
((startB + (int) (fraction * (endB - startB))));
}
titleView.setBackgroundColor(ColorUtil.getNewColorByStartEndColor(this, (float)((scrollY * 1.0 / (stopView.getTop())) > 1 ? 1 : (scrollY * 1.0 / (stopView.getTop()))), R.color.colorTransparent, R.color.colorWhite));
通过scrollY与悬停View的初始位置的比值计算出透明度的值。
d,ListView插入ScrollView的正确方式
之所以说正确方式,首先我们看看错误的方法,我直接把ListView插入ScrollView
我相信很多同学都遇到同样的问题,不只是ListView,GridView也是一样的,都会出问题。我在网上了百度了一下,找到了2个解决办法,这里我贴出一种解决办法:
public class MyXListView extends ListView {
public MyXListView(Context context) {
this(context,null);
}
public MyXListView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyXListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 重写该方法,达到使ListView适应ScrollView的效果
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
该方法也同样适用于GridView.好!看一下我把ListView改成MyXListView之后的效果:
一切都还好就是屏幕显示有点问题,为什么自动滑到了ListView视图上而不是ScrollView的顶部呢?既然它自动滑到了ListView上,那我就用2行代码把它滑动上去吧!
observableScrollView.scrollTo(0, 0);
observableScrollView.smoothScrollTo(0, 0);//设置scrollView默认滚动到顶部
这样看起来就是我想要的效果了。对吧!最后一个问题思考
e,这样效率怎么样?
相信细心的人已经看出来了,我每次进入的时候会有一会儿的白屏,那会儿应该是在绘制视图,我只是在LIstView加载100个item额。来我们绘制1000个试试。
已经很明显了。
为什么会出现这种情况呢?原因是我重写了ListView一次就加载所有的item,从而导致了item根本没有重用。那为什么要一次性加载所有的item呢?还不是为了知道ListView的高度,由于ScrollView镶嵌ListView导致无法正确的测绘出ListView的高度,我们需要提前让ScrollView知道ListView的高度,好正确的显示ListView.所以这种方法显示小量的数据还可以,大量的数据就显得无力了。
二,用ListView增加头部实现。
ListView的实现我是在StickyHeaderListView的基础上进行改进的,StickyHeaderListView的原文地址:http://www.open-open.com/lib/view/open1461744699190.html
先来看看效果:
我改了2个地方:
1,补空视图
StickyHeaderListView的那个补空视图是先设置一个ONE_SCREEN_COUNT(一屏能显示的个数,这个根据屏幕高度和各自的需求定),然后用下面代码添加数据:
// 设置数据
public void setData(List<TravelingEntity> list) {
clearAll();
addALL(list);
isNoData = false;
if (list.size() == 1 && list.get(0).isNoData()) {
// 暂无数据布局
isNoData = list.get(0).isNoData();
mHeight = list.get(0).getHeight();
} else {
// 添加空数据
if (list.size() < ONE_SCREEN_COUNT) {
addALL(createEmptyList(ONE_SCREEN_COUNT - list.size()));
}
}
notifyDataSetChanged();
}
可以看到当list.size()<ONE_SCREEN_COUNT时就开始添加空数据,添加空数据的个数为ONE_SCREEN_COUNT-list.size()。
然后我们看看它的getView()代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 暂无数据
if (isNoData) {
convertView = mInflater.inflate(R.layout.item_no_data_layout, null);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeight);
RelativeLayout rootView = ButterKnife.findById(convertView, R.id.rl_root_view);
rootView.setLayoutParams(params);
return convertView;
}
// 正常数据
final ViewHolder holder;
if (convertView != null && convertView instanceof LinearLayout) {
holder = (ViewHolder) convertView.getTag();
} else {
convertView = mInflater.inflate(R.layout.item_travel, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
TravelingEntity entity = getItem(position);
holder.llRootView.setVisibility(View.VISIBLE);
if (TextUtils.isEmpty(entity.getType())) {
holder.llRootView.setVisibility(View.INVISIBLE);
return convertView;
}
holder.tvTitle.setText(entity.getFrom() + entity.getTitle() + entity.getType());
holder.tvRank.setText("排名:" + entity.getRank());
mImageManager.loadUrlImage(entity.getImage_url(), holder.ivImage);
return convertView;
}
可以看到当他加载到空数据的时候采用了holder.llRootView.setVisibility(View.INVISIBLE);来达到补空视图将数据占满整个屏幕的效果。而空视图的个数为ONE_SCREEN_COUNT-list.size(),空视图和正常视图效果一样,只是显示为VIEW.INVISIBLE。而ONE_SCREEN_COUNT很难控制,所以空视图的个数也就很难控制,这样就造成整个空视图过高,而可以把正常数据滑出屏幕。
既然找到原因,代码优化:
/**
* 给适配填充数据源,用来显示数据
*
* @param list
*/
public void setData(List<T> list) {
isNoData = false;
clearAll();
addAll(list);
//当可用数据少于设置的一屏数据时添加空数据将显示撑满一屏
if (mList.size() < ONE_SCREEN_COUNT) {
addAll(addEmptyData(1));
}
notifyDataSetChanged();
}
我的想法是只要list.size()<ONE_SCREEN_COUNT,就添加一个空视图,空视图不与正常数据公用一个视图,而是自己独立的一个视图,空视图的高度通过计算获得。这样ONE_SCREEN_COUNT的数值就不需要特别准备,写大点准没错(当然我不推荐,自己估摸着来,不需要特别准确)
那么空视图的高度怎么计算呢?其实也很简单的:
switch (type) {
//这个视图有可能是空视图,也有可能是补空视图(就是有数据,但是不足以占满整个屏幕时,增加的视图将其占满整个视图)
//当getCount()为1,就是空视图,getCount()>1就是补空视图
case VIEW_TYPE_2:
AbsListView.LayoutParams params = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
(AndroidUtils.getScreenSize()[1] - getItemViewHeigh(parent) * (getCount() - 1) - mHeight));
convertView.setLayoutParams(params);
if (getCount() != 1) {//标识有填充空数据
convertView.setVisibility(View.INVISIBLE);
} else {//就只有一个空视图
convertView.setVisibility(View.VISIBLE);
}
break;
}
这里有一个非常重要的方法要提一下就是获取listview的item的高度的方法:
/**
* 获取ListViewItem的高度
*
* @param parent
* @return
*/
private int getItemViewHeigh(ViewGroup parent) {
View view = getView(0, null, parent);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
return view.getMeasuredHeight();
}
总的思想就是获取屏幕的高度,然后测量ListView的item的高度。然后用屏幕的高度减去所有正常数据的item的高度就是空视图所需要的高度。
2.接口回调
StickyHeaderListView中的悬浮视图的真视图(真正的悬浮视图,响应点击事件的View)和假视图(只做显示用View)之间用了很多接口回调来传递点击事件,看得人比较头晕,这里我将它们全部替换成了RxBus,详情可以查看项目。
总结:ScrollView+ListView实现顶部悬停代码比较简单,但是ScrollView+ListView的嵌套导致了ListView的item重用机制失效,所以在处理大量数据显示的时候会大大的降低效率。ListView增加头部实现代码逻辑比较复杂,但是它保留了ListView的item重用的机制,在处理大量数据显示的时候效率也没有降低。所以在数据小而且简单功能的时候ScrollView+ListView会比较实用,但是数据量大而且功能复杂我还是推荐实用ListView增加头部实现悬停。
项目地址:http://download.csdn.net/detail/baidu_34012226/9620653