import java.util.ArrayList; public class StickyScrollView extends ScrollView { /** * Tag for views that should stick and have constant drawing. e.g. TextViews, ImageViews etc */ public static final String STICKY_TAG = "sticky"; /** * Flag for views that should stick and have non-constant drawing. e.g. Buttons, ProgressBars etc */ public static final String FLAG_NONCONSTANT = "-nonconstant"; /** * Flag for views that have aren't fully opaque */ public static final String FLAG_HASTRANSPARANCY = "-hastransparancy"; private ArrayList<View> stickyViews; private View currentlyStickingView; private float stickyViewTopOffset; private boolean redirectTouchesToStickyView; private boolean clippingToPadding; private boolean clipToPaddingHasBeenSet; private final Runnable invalidateRunnable = new Runnable() { @Override public void run() { if(currentlyStickingView!=null){ int l = getLeftForViewRelativeOnlyChild(currentlyStickingView); int t = getBottomForViewRelativeOnlyChild(currentlyStickingView); int r = getRightForViewRelativeOnlyChild(currentlyStickingView); int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset)); invalidate(l,t,r,b); } postDelayed(this, 16); } }; public StickyScrollView(Context context) { this(context, null); } public StickyScrollView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.scrollViewStyle); } public StickyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setup(); } public void setup(){ stickyViews = new ArrayList<View>(); } private int getLeftForViewRelativeOnlyChild(View v){ int left = v.getLeft(); while(v.getParent() != getChildAt(0)){ v = (View) v.getParent(); left += v.getLeft(); } return left; } private int getTopForViewRelativeOnlyChild(View v){ int top = v.getTop(); while(v.getParent() != getChildAt(0)){ v = (View) v.getParent(); top += v.getTop(); } return top; } private int getRightForViewRelativeOnlyChild(View v){ int right = v.getRight(); while(v.getParent() != getChildAt(0)){ v = (View) v.getParent(); right += v.getRight(); } return right; } private int getBottomForViewRelativeOnlyChild(View v){ int bottom = v.getBottom(); while(v.getParent() != getChildAt(0)){ v = (View) v.getParent(); bottom += v.getBottom(); } return bottom; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(!clipToPaddingHasBeenSet){ clippingToPadding = true; } notifyHierarchyChanged(); } @Override public void setClipToPadding(boolean clipToPadding) { super.setClipToPadding(clipToPadding); clippingToPadding = clipToPadding; clipToPaddingHasBeenSet = true; } @Override public void addView(View child) { super.addView(child); findStickyViews(child); } @Override public void addView(View child, int index) { super.addView(child, index); findStickyViews(child); } @Override public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) { super.addView(child, index, params); findStickyViews(child); } @Override public void addView(View child, int width, int height) { super.addView(child, width, height); findStickyViews(child); } @Override public void addView(View child, android.view.ViewGroup.LayoutParams params) { super.addView(child, params); findStickyViews(child); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if(currentlyStickingView != null){ canvas.save(); canvas.translate(getPaddingLeft(), getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight()); if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){ showView(currentlyStickingView); currentlyStickingView.draw(canvas); hideView(currentlyStickingView); }else{ currentlyStickingView.draw(canvas); } canvas.restore(); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if(ev.getAction()==MotionEvent.ACTION_DOWN){ redirectTouchesToStickyView = true; } if(redirectTouchesToStickyView){ redirectTouchesToStickyView = currentlyStickingView != null; if(redirectTouchesToStickyView){ redirectTouchesToStickyView = ev.getY()<=(currentlyStickingView.getHeight()+stickyViewTopOffset) && ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView) && ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView); } }else if(currentlyStickingView == null){ redirectTouchesToStickyView = false; } if(redirectTouchesToStickyView){ ev.offsetLocation(0, -1*((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView))); } return super.dispatchTouchEvent(ev); } private boolean hasNotDoneActionDown = true; @Override public boolean onTouchEvent(MotionEvent ev) { if(redirectTouchesToStickyView){ ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView))); } if(ev.getAction()==MotionEvent.ACTION_DOWN){ hasNotDoneActionDown = false; } if(hasNotDoneActionDown){ MotionEvent down = MotionEvent.obtain(ev); down.setAction(MotionEvent.ACTION_DOWN); super.onTouchEvent(down); hasNotDoneActionDown = false; } if(ev.getAction()==MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL){ hasNotDoneActionDown = true; } return super.onTouchEvent(ev); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); doTheStickyThing(); } private void doTheStickyThing() { View viewThatShouldStick = null; View approachingView = null; for(View v : stickyViews){ int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()); if(viewTop<=0){ if(viewThatShouldStick==null || viewTop>(getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))){ viewThatShouldStick = v; } }else{ if(approachingView == null || viewTop<(getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))){ approachingView = v; } } } if(viewThatShouldStick!=null){ stickyViewTopOffset = approachingView == null ? 0 : Math.min(0, getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight()); if(viewThatShouldStick != currentlyStickingView){ if(currentlyStickingView!=null){ stopStickingCurrentlyStickingView(); } startStickingView(viewThatShouldStick); } }else if(currentlyStickingView!=null){ stopStickingCurrentlyStickingView(); } } private void startStickingView(View viewThatShouldStick) { currentlyStickingView = viewThatShouldStick; if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){ hideView(currentlyStickingView); } if(((String)currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)){ post(invalidateRunnable); } } private void stopStickingCurrentlyStickingView() { if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){ showView(currentlyStickingView); } currentlyStickingView = null; removeCallbacks(invalidateRunnable); } /** * Notify that the sticky attribute has been added or removed from one or more views in the View hierarchy */ public void notifyStickyAttributeChanged(){ notifyHierarchyChanged(); } private void notifyHierarchyChanged(){ if(currentlyStickingView!=null){ stopStickingCurrentlyStickingView(); } stickyViews.clear(); findStickyViews(getChildAt(0)); doTheStickyThing(); invalidate(); } private void findStickyViews(View v) { if(v instanceof ViewGroup){ ViewGroup vg = (ViewGroup)v; for(int i = 0 ; i<vg.getChildCount() ; i++){ String tag = getStringTagForView(vg.getChildAt(i)); if(tag!=null && tag.contains(STICKY_TAG)){ stickyViews.add(vg.getChildAt(i)); }else if(vg.getChildAt(i) instanceof ViewGroup){ findStickyViews(vg.getChildAt(i)); } } }else{ String tag = (String) v.getTag(); if(tag!=null && tag.contains(STICKY_TAG)){ stickyViews.add(v); } } } private String getStringTagForView(View v){ Object tagObject = v.getTag(); return String.valueOf(tagObject); } private void hideView(View v) { if(Build.VERSION.SDK_INT>=11){ // v.setAlpha(0); }else{ AlphaAnimation anim = new AlphaAnimation(1, 0); anim.setDuration(0); anim.setFillAfter(true); v.startAnimation(anim); } } private void showView(View v) { if(Build.VERSION.SDK_INT>=11){ // v.setAlpha(1); }else{ AlphaAnimation anim = new AlphaAnimation(0, 1); anim.setDuration(0); anim.setFillAfter(true); v.startAnimation(anim); } } }
然后在需要显示的UI布局中添加
android:tag="sticky-nonconstant-hastransparancy"
就OK了
相关推荐
综上所述,"防QQListView滑动置顶删除功能"是Android开发中提高用户体验的一个重要实践,涉及到手势识别、自定义视图、UI动画、数据操作等多个方面。理解并掌握这一功能的实现原理,对于提升Android应用的交互设计...
总结来说,"android ScrollView滑动置顶"是一种常见的UI交互效果,可以通过多种方式实现,如ObservableScrollView、自定义ScrollView、AppBarLayout和CollapsingToolbarLayout,或者是使用CoordinatorLayout配合...
这个效果通常是指当用户向上滑动ListView时,某些特定视图(如广告或者搜索框)会固定在屏幕顶部,直到用户再次滑动下去才隐藏。实现这个效果的方法有多种,一种是使用布局嵌套,将吸顶视图放在ListView之上,通过...
总之,“仿QQ消息SwipeMenuListView滑动删除置顶”是一个涵盖了Android UI设计、事件处理、动画效果和业务逻辑等多个方面的项目。通过学习和实践,开发者不仅可以提升Android编程技能,还能更好地理解和应用用户界面...
5. **用户反馈**:在执行删除或置顶操作后,更新UI以反映变化,例如,如果置顶,将该条目移动到列表顶部;如果删除,从列表中移除对应条目。 6. **异常处理**:确保在所有可能的异常情况下,如网络错误或数据异常,...
`ListView的置顶与滑动监听`这个主题涵盖了两个重要的技术点:如何实现ListView的顶部固定效果(类似置顶功能)以及如何添加滑动监听事件来响应用户的滚动操作。 首先,让我们详细探讨ListView的置顶功能。置顶通常...
QQ未读消息气泡拖拽和滑动删除置顶是一种常见的移动应用交互设计,常见于即时通讯软件中,如QQ。这种设计提升了用户体验,让用户能够更直观、便捷地管理未读消息。以下将详细讲解这一功能的技术实现和相关知识点。 ...
此外,为了实现类似微信的滑动置顶效果,我们可以监听`RecyclerView`的滚动事件,通过`OnScrollListener`的`onScrolled`方法。当用户向上滑动时,检查当前可视的第一个聊天项是否已置顶。如果是,我们需要更新布局,...
在这里,我们可以为每个Item视图添加一个可滑动的布局,初始时隐藏删除按钮。当用户左滑时,改变这个布局的状态,显示删除按钮。 3. **动画效果**:为了让用户体验更佳,可以添加滑动动画。在滑动过程中,通过`...
在这个自定义cell中,你需要添加三个按钮(删除、置顶和收藏)并隐藏它们。这些按钮的布局应与屏幕边缘对齐,以便在滑动时能够露出。 3. **滑动手势处理:** 添加UISwipeGestureRecognizer到你的cell,通常是向左...
图片循环播放是一种动态展示多张图片的UI设计,常用于轮播图或幻灯片效果。实现这一功能通常需要使用JavaScript库或CSS动画。例如,可以使用jQuery的`carousel`插件或纯CSS的`transition`和`animation`属性。核心...
在Android开发中,为了增强应用的交互性和用户体验,经常需要实现一些高级的UI特性,比如仿QQ消息的滑动删除和置顶功能。这个功能在许多聊天应用中都非常常见,它允许用户通过简单的手势操作来管理消息列表。下面...
开发者可以通过在应用程序的样式XML文件中设置`<item name="android:windowTranslucentStatus">true</item>`,或者在Java代码中使用`View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`和`View.SYSTEM_UI_FLAG_LAYOUT_STABLE`...
总之,实现“PullToRefreshListView顶部轮播图”需要综合运用Android的UI组件、事件处理、动画、性能优化以及第三方库的集成等技能。通过合理的设计和编程,可以创建一个既美观又实用的界面,提高用户在浏览信息时的...
在Android开发中,滑动定位和吸附悬停效果是常见的UI交互设计,通常用于创建类似锚点导航或顶部标签栏的行为。这些功能可以增强用户的浏览体验,使得用户能够轻松地在长页面中定位和切换不同的内容区域。本文将详细...
在iOS开发中,获取手机通讯录、自定义Cell展示、实现Cell左滑菜单以及某条记录置顶效果是常见的功能需求。以下将详细介绍这些知识点...通过研究其代码,可以深入理解iOS开发中的数据获取、UI设计和交互实现等核心技能。
在处理滑动删除的过程中,你还需要考虑一些额外的细节,比如手势的取消、滑动速度的控制、动画效果的平滑度,以及在删除item后的数据更新和UI刷新。最后,别忘了在adapter中适配这个自定义的SlideListView,确保每个...
通过为子视图定义特定的Behavior,可以实现各种自定义的交互效果,如滑动隐藏/显示Toolbar。 最后,我们注意到**相对布局(RelativeLayout)** 的嵌套使用。RelativeLayout允许视图相对于其他视图或者布局边缘进行...
需要注意的是,`Handler.post(runnable)`并不会开启新的线程,而是将任务放入UI主线程的消息队列中执行。这是因为Android的UI操作必须在主线程中进行。同时,使用Handler确保了View的绘制完成,避免在视图未准备好时...