个人主页常见的头像与背景图不同步移动的下拉效果实现
来源:互联网 发布:成都电视台网络直播 编辑:程序博客网 时间:2024/06/06 00:31
前言
我自己想出来的实现方式,而且我觉得这样实现效率最高。
效果图
原理
- 假设背景图比用户信息视图高x,那么将背景的顶部x/2和底部x/2隐藏起来,如图所示
- 下拉的时候,让背景的移动速度是内容移动速度的1/2
- 下拉到一定程度,背景完全显示之后,让背景和内容的移动速度保持一致
如何隐藏背景图的顶部和底部?
通过设置背景的marginTop为-x/2隐藏顶部,而个人资料视图下面的视图会把背景图底部x/2挡住。
为什么要让背景的移动速度是内容移动速度的1/2?
这样的话,背景的顶部和底部都会慢慢可见,效果更佳。顶部慢慢可见是因为背景有向下的移动速度。底部慢慢可见是因为背景向下的移动速度小于内容的向下移动速度。
Demo
Github
布局
<com.example.myronlg.asyncscrollviewdemo.AsyncScrollFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/asv" android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".MainActivity"> <!-- 背景 --> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/bg" /> <!-- 内容 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 用户信息 --> <LinearLayout android:id="@+id/user_info_container" android:layout_width="match_parent" android:layout_height="@dimen/bg_visual_height" android:gravity="center" android:orientation="vertical"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/potrait" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/potrait" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="myron.loop" android:textColor="@android:color/primary_text_dark" /> </LinearLayout> <com.astuetz.PagerSlidingTabStrip android:id="@+id/tv_anchor" android:layout_width="match_parent" android:layout_height="@dimen/anchor_height" android:layout_below="@+id/user_info_container" android:background="@android:color/white" /> <android.support.v4.view.ViewPager android:id="@+id/vp" android:layout_width="match_parent" android:layout_height="1000dp" android:layout_below="@+id/tv_anchor" android:visibility="visible" /> </LinearLayout></com.example.myronlg.asyncscrollviewdemo.AsyncScrollFrameLayout>
关键源码
隐藏背景的顶部和底部
float bgDrawableHeight = getBgDrawableHeight(); float bgDrawableVisibleHeight = userInfoView.getMeasuredHeight(); bgDrawableHiddenHeight = (int) (bgDrawableHeight - bgDrawableVisibleHeight); FrameLayout.LayoutParams bgViewLayoutParams = (LayoutParams) bgView.getLayoutParams(); bgViewLayoutParams.setMargins(0, -bgDrawableHiddenHeight/2, 0, 0);
移动背景和内容
if (fgView.getScrollY() <= -bgDrawableHiddenHeight) {//if the bgView is shown entirely, sync scroll scrollBy(0, computeDy(-dy * 0.5F)); } else {//if the bgView is not shown entirely, async scroll fgView.scrollBy(0, computeDy(-dy * 0.5F)); bgView.scrollBy(0, computeDy(-dy * 0.25F)); }
完整源码
public class AsyncScrollFrameLayout extends FrameLayout { private View bgView; private View fgView; private ViewPager viewPager; private float lastY = -1; private Scroller scroller; /** * A value that indicate who should scroll, this frameLayout or inner scrollable view. * If the inner scrollable view is entirely visible, let it scroll. Otherwise, let this frameLayout scroll. */ private int interceptThreshold; private boolean intercept; private int bgDrawableHiddenHeight; public AsyncScrollFrameLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public AsyncScrollFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public AsyncScrollFrameLayout(Context context) { super(context); init(); } private void init() { scroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator()); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); bgView = getChildAt(0); fgView = getChildAt(1); View userInfoView = ((ViewGroup) fgView).getChildAt(0); View anchorView = ((ViewGroup) fgView).getChildAt(1); viewPager = (ViewPager) ((ViewGroup) fgView).getChildAt(2); interceptThreshold = anchorView.getTop(); float bgDrawableHeight = getBgDrawableHeight(); float bgDrawableVisibleHeight = userInfoView.getMeasuredHeight(); bgDrawableHiddenHeight = (int) (bgDrawableHeight - bgDrawableVisibleHeight); FrameLayout.LayoutParams bgViewLayoutParams = (LayoutParams) bgView.getLayoutParams(); bgViewLayoutParams.setMargins(0, -bgDrawableHiddenHeight/2, 0, 0); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) viewPager.getLayoutParams(); layoutParams.height = getMeasuredHeight() - anchorView.getMeasuredHeight(); viewPager.setLayoutParams(layoutParams); } private float getBgDrawableHeight() { ImageView bgImageView = (ImageView) bgView; Drawable bgDrawable = bgImageView.getDrawable(); return ((float) bgDrawable.getIntrinsicHeight()) * bgImageView.getMeasuredWidth() / bgDrawable.getIntrinsicWidth(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return handleTouchEvent(ev); }/* 不要去重写这两个函数,想要完全掌控触摸事件的传递和处理你必须要重写dispatchTouchEvent(),而重写dispatchTouchEvent就足够完全掌控触摸事件的传递和处理 因为这里涉及一个手势的前半部分由一个控件处理,后半部分由另一个控件处理的情况,Android事件处理框架是把一个手势交给一个控件处理的。 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (getScrollY() < interceptThreshold) { outerScrolling = true; return true; } else { if (outerScrolling) { dispathDownEventMannualy(); } } return super.onInterceptTouchEvent(ev); return intercept; } @Override public boolean onTouchEvent(MotionEvent ev) { return true; }*/ private boolean handleTouchEvent(MotionEvent ev) { if (lastY == -1) { lastY = ev.getY(); } boolean flag = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: flag = onDown(ev); break; case MotionEvent.ACTION_MOVE: flag = onMove(ev); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: flag = onEnd(ev); break; default: } lastY = ev.getY(); return flag; } private boolean onDown(MotionEvent ev) { super.dispatchTouchEvent(ev); return true; } private boolean onMove(MotionEvent ev) { float dy = ev.getY() - lastY; //this container take care of scrolling if (fgView.getScrollY() < interceptThreshold) { // pull down over scroll if (fgView.getScrollY() < 0 || (fgView.getScrollY() == 0 && dy > 0)) { if (fgView.getScrollY() <= -bgDrawableHiddenHeight) {//if the bgView is shown entirely, sync scroll scrollBy(0, computeDy(-dy * 0.5F)); } else {//if the bgView is not shown entirely, async scroll fgView.scrollBy(0, computeDy(-dy * 0.5F)); bgView.scrollBy(0, computeDy(-dy * 0.25F)); } } else {//pull up int scrollYDelta = computeDy(-dy); if (fgView.getScrollY()+scrollYDelta < 0){ // let async scroll take care from here scrollBy(0, -fgView.getScrollY()); } else if (fgView.getScrollY()+scrollYDelta >= interceptThreshold) { // let bottom scrollable view take care from here scrollBy(0, interceptThreshold -fgView.getScrollY()); } else { scrollBy(0, scrollYDelta); } } intercept = true; return true; } else { if (isViewPagerReachTop() && dy > 0){ if (!intercept) { fgView.setScrollY(interceptThreshold); bgView.setScrollY(interceptThreshold); dispatchCancelEvent(ev); } int scrollYDelta = computeDy(-dy); scrollBy(0, scrollYDelta);// this is 99% impossible// if (fgView.getScrollY()+scrollYDelta < 0){// scrollBy(0, -fgView.getScrollY());// } else {// scrollBy(0, scrollYDelta);// } intercept = true; return true; } else { if (intercept){ dispatchDownEvent(ev); } intercept = false; return super.dispatchTouchEvent(ev); } } } private void dispatchCancelEvent(MotionEvent ev){ MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(cancelEvent); } private void dispatchDownEvent(MotionEvent ev){ MotionEvent downEvent = MotionEvent.obtain(ev); downEvent.setAction(MotionEvent.ACTION_DOWN); super.dispatchTouchEvent(downEvent); } private boolean onEnd(MotionEvent ev) { if (fgView.getScrollY() <= 0) { scroller.startScroll(bgView.getScrollY(), fgView.getScrollY(), -bgView.getScrollY(), -fgView.getScrollY()); invalidate(); } lastY = -1; intercept = false; return super.dispatchTouchEvent(ev); } private boolean isViewPagerReachTop() { int currentIndex = viewPager.getCurrentItem(); View currentPage = viewPager.findViewWithTag(currentIndex); return isViewReachTop(currentPage); } private boolean isViewReachTop(View view) { if (view instanceof AdapterView<?>) { AdapterView<?> adapterView = (AdapterView<?>) view; if (adapterView.getLastVisiblePosition() == 0 && adapterView.getChildAt(0).getTop() >= 0) { return true; } else { return false; } } else { if (view.getScrollY() <= 0) { return true; } else { return false; } } } @Override public void scrollBy(int x, int y) { fgView.scrollBy(x, y); bgView.scrollBy(x, y); } private int computeDy(float dy){ if (dy > 0){ return Math.round(dy+0.5F); } else { return Math.round(dy-0.5F); } } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { fgView.scrollTo(0, scroller.getCurrY()); bgView.scrollTo(0, scroller.getCurrX()); postInvalidate(); } else { super.computeScroll(); } }}
1 0
- 个人主页常见的头像与背景图不同步移动的下拉效果实现
- 实现类似于Vowch的头像移动的动画效果。
- 实现左右移动的下拉菜单效果
- 实现背景图自适应效果的jQuery插件
- jQuery实现两个下拉菜单的选项互相移动效果
- android动画:头像在两个界面的移动效果
- 设置磨砂效果的背景图
- 新浪微博个人主页效果实现(头像随tableview滑动)
- 多线程的同步与不同步
- 移动端拖拽的实现效果
- 实现图片下拉放大和导航栏头像缩放效果
- Android 使用PullToZoomScrollViewEx实现头像下拉缩放动态效果
- 空间背景图的简单的视差效果
- 背景图跟随鼠标移动的Mootools插件
- 利用铁匠组件实现在线头像截图的效果
- Android效果:FrameLayout实现中间头像的Dialog
- JS结合七牛实现上传头像的效果
- android之超级简单的下拉回弹--仿QQ个人主页
- JAVA入门基础文章3-类的概念-什么是继承
- 文本文件解析
- Java比较String ==和equals() 解析
- 这样的人注定生活在社会最底层
- JavaWeb 全站乱码过滤
- 个人主页常见的头像与背景图不同步移动的下拉效果实现
- CF 550C 数学 or dp
- window.open 打开子窗口,关闭所有的子窗口
- 黑马程序员--学习OC类方法
- Android播放音频的两种方式
- 悼念512汶川大地震遇难同胞——选拔志愿者
- 简约至上-交互是设计四策略读书感
- 5. CSS 类选择器
- Android之通过网络播放一首简单的音乐