`
龙哥IT
  • 浏览: 254572 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

UI滑动置顶不隐藏

 
阅读更多
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滑动置顶删除功能

    综上所述,"防QQListView滑动置顶删除功能"是Android开发中提高用户体验的一个重要实践,涉及到手势识别、自定义视图、UI动画、数据操作等多个方面。理解并掌握这一功能的实现原理,对于提升Android应用的交互设计...

    android ScrollView滑动置顶

    总结来说,"android ScrollView滑动置顶"是一种常见的UI交互效果,可以通过多种方式实现,如ObservableScrollView、自定义ScrollView、AppBarLayout和CollapsingToolbarLayout,或者是使用CoordinatorLayout配合...

    Android-滑动listview标题置顶listview吸顶效果

    这个效果通常是指当用户向上滑动ListView时,某些特定视图(如广告或者搜索框)会固定在屏幕顶部,直到用户再次滑动下去才隐藏。实现这个效果的方法有多种,一种是使用布局嵌套,将吸顶视图放在ListView之上,通过...

    仿QQ消息SwipeMenuListView滑动删除置顶

    总之,“仿QQ消息SwipeMenuListView滑动删除置顶”是一个涵盖了Android UI设计、事件处理、动画效果和业务逻辑等多个方面的项目。通过学习和实践,开发者不仅可以提升Android编程技能,还能更好地理解和应用用户界面...

    android 仿微信列表滑动出现删除,置顶效果

    5. **用户反馈**:在执行删除或置顶操作后,更新UI以反映变化,例如,如果置顶,将该条目移动到列表顶部;如果删除,从列表中移除对应条目。 6. **异常处理**:确保在所有可能的异常情况下,如网络错误或数据异常,...

    ListView的置顶与滑动监听

    `ListView的置顶与滑动监听`这个主题涵盖了两个重要的技术点:如何实现ListView的顶部固定效果(类似置顶功能)以及如何添加滑动监听事件来响应用户的滚动操作。 首先,让我们详细探讨ListView的置顶功能。置顶通常...

    模仿qq未读消息气泡拖拽和滑动删除置顶

    QQ未读消息气泡拖拽和滑动删除置顶是一种常见的移动应用交互设计,常见于即时通讯软件中,如QQ。这种设计提升了用户体验,让用户能够更直观、便捷地管理未读消息。以下将详细讲解这一功能的技术实现和相关知识点。 ...

    仿微信聊天置顶

    此外,为了实现类似微信的滑动置顶效果,我们可以监听`RecyclerView`的滚动事件,通过`OnScrollListener`的`onScrolled`方法。当用户向上滑动时,检查当前可视的第一个聊天项是否已置顶。如果是,我们需要更新布局,...

    模仿QQListView左滑删除置顶Item功能

    在这里,我们可以为每个Item视图添加一个可滑动的布局,初始时隐藏删除按钮。当用户左滑时,改变这个布局的状态,显示删除按钮。 3. **动画效果**:为了让用户体验更佳,可以添加滑动动画。在滑动过程中,通过`...

    ios-类似qq侧滑出现的删除置顶 收藏.zip

    在这个自定义cell中,你需要添加三个按钮(删除、置顶和收藏)并隐藏它们。这些按钮的布局应与屏幕边缘对齐,以便在滑动时能够露出。 3. **滑动手势处理:** 添加UISwipeGestureRecognizer到你的cell,通常是向左...

    图片循环播放 、侧滑、点击,仿淘宝

    图片循环播放是一种动态展示多张图片的UI设计,常用于轮播图或幻灯片效果。实现这一功能通常需要使用JavaScript库或CSS动画。例如,可以使用jQuery的`carousel`插件或纯CSS的`transition`和`animation`属性。核心...

    Android开发实现仿QQ消息SwipeMenuListView滑动删除置顶功能【附源码下载】

    在Android开发中,为了增强应用的交互性和用户体验,经常需要实现一些高级的UI特性,比如仿QQ消息的滑动删除和置顶功能。这个功能在许多聊天应用中都非常常见,它允许用户通过简单的手势操作来管理消息列表。下面...

    沉浸式状态栏+头部拉伸+一键置顶

    开发者可以通过在应用程序的样式XML文件中设置`&lt;item name="android:windowTranslucentStatus"&gt;true&lt;/item&gt;`,或者在Java代码中使用`View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`和`View.SYSTEM_UI_FLAG_LAYOUT_STABLE`...

    PullToRefreshListView顶部轮播图的简单实现(可以扩展到类似贴吧置顶贴的功能)

    总之,实现“PullToRefreshListView顶部轮播图”需要综合运用Android的UI组件、事件处理、动画、性能优化以及第三方库的集成等技能。通过合理的设计和编程,可以创建一个既美观又实用的界面,提高用户在浏览信息时的...

    Android 滑动定位和吸附悬停效果实现代码

    在Android开发中,滑动定位和吸附悬停效果是常见的UI交互设计,通常用于创建类似锚点导航或顶部标签栏的行为。这些功能可以增强用户的浏览体验,使得用户能够轻松地在长页面中定位和切换不同的内容区域。本文将详细...

    ios-获取手机通讯录,自定义cell展示,cell 左滑菜单,某条记录 置顶效果.zip

    在iOS开发中,获取手机通讯录、自定义Cell展示、实现Cell左滑菜单以及某条记录置顶效果是常见的功能需求。以下将详细介绍这些知识点...通过研究其代码,可以深入理解iOS开发中的数据获取、UI设计和交互实现等核心技能。

    Android开发中模仿qq列表信息滑动删除功能

    在处理滑动删除的过程中,你还需要考虑一些额外的细节,比如手势的取消、滑动速度的控制、动画效果的平滑度,以及在删除item后的数据更新和UI刷新。最后,别忘了在adapter中适配这个自定义的SlideListView,确保每个...

    SlideLayoutDemo.rar

    通过为子视图定义特定的Behavior,可以实现各种自定义的交互效果,如滑动隐藏/显示Toolbar。 最后,我们注意到**相对布局(RelativeLayout)** 的嵌套使用。RelativeLayout允许视图相对于其他视图或者布局边缘进行...

    android scrollview 滑动到顶端或者指定位置的实现方法

    需要注意的是,`Handler.post(runnable)`并不会开启新的线程,而是将任务放入UI主线程的消息队列中执行。这是因为Android的UI操作必须在主线程中进行。同时,使用Handler确保了View的绘制完成,避免在视图未准备好时...

Global site tag (gtag.js) - Google Analytics