Android视图的顶部悬停的实现

何为视图的顶部悬停呢?上图看吧:

我这里给出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


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Studio中,可以使用视图绑定(ViewBinding)来替代findViewById方法,以更方便地访问和操作视图元素。下面是使用视图绑定实现视图的步骤: 1. 确保你的项目已经升级到Android Studio 3.6 Canary 11或更高版本。 2. 在项目的build.gradle文件中,将以下代码添加到android块中: ```groovy viewBinding { enabled = true } ``` 3. 在布局文件中,使用<layout>标签将布局文件包裹起来,例如: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 布局内容 --> </LinearLayout> </layout> ``` 4. 在Activity或Fragment中,使用以下代码来获取视图绑定实例: ```java // 对于Activity private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // 现在可以通过binding对象访问布局中的视图元素了 binding.textView.setText("Hello World!"); } // 对于Fragment private FragmentMainBinding binding; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentMainBinding.inflate(inflater, container, false); View rootView = binding.getRoot(); // 现在可以通过binding对象访问布局中的视图元素了 binding.textView.setText("Hello World!"); return rootView; } ``` 通过使用视图绑定,你可以直接使用布局文件中定义的视图元素,而无需手动调用findViewById方法。这样可以提高代码的可读性和开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值