先上效果图和网易新闻的对比
网易新闻
我实现的
基本效果实现了,后面还需要进一步优化。实现的答题思路就是通过拦截滑动事件和调用layout()
的实现控件的拖动和高度的限制,内容的高度是由TextView
的maxHeight
和minHeight
来限制的。
初始化
新建一个类继承LinearLayout
这个类就是我们布局容器,首先初始化这个类,需要定义一些自定义参数
/** * 初始化View * @param context * @param attrs */ private void initView(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.bot_arr); String title = ta.getString(R.styleable.bot_arr_bot_title); String content = ta.getString(R.styleable.bot_arr_bot_content); ta.recycle(); LayoutInflater inflater = LayoutInflater.from(context); mView = inflater.inflate(R.layout.bottom_layout2, this, true); mTitleView = (TextView) mView.findViewById(R.id.bot_title_text); mContentView = (TextView) mView.findViewById(R.id.bot_content); mPageNum = (TextView) mView.findViewById(R.id.page_num); mTitleLayout = (LinearLayout) mView.findViewById(R.id.title_layout); mOuterLayout= (LinearLayout)mView.findViewById(R.id.outer_layout); mContentView.setText(content); //设置TextView可以滚动 mContentView.setMovementMethod(new ScrollingMovementMethod()); mTitleView.setText(title); this.context = context; }
基本都是一些很简单的东西,但是有一句很关键的代码mContentView.setMovementMethod(new ScrollingMovementMethod());
,当我们设置了TextView
的maxHeight
和minHeight
后当字数超过了限制我们当然希望是可以滑动的,但是TextView
是无法滑动的。最开始我的想法是用ScrollView
嵌套一个TextView
但是ScrollView
没有限制高度的方法,要自己去计算最高和最低的高度很麻烦。最后在Stackoverflow上看到了解决方法,使用setMovementMethod
方法,我们看看官方文档的解释
什么?看不懂?好吧我就是放上来装逼的。Android
中我们为了实现文本的滚动可以在ScrollView
中嵌入一个TextView
,其实TextView
自己也可以实现多行滚动的,只需要设置一些属性
<TextView android:id="@+id/bot_content" android:layout_width="match_parent" android:layout_height="wrap_content" //最高高度 android:maxHeight="200dp" //最低高度 android:minHeight="100dp" android:padding="5dp" //滚动反向 android:scrollbars="vertical" android:text="xxxx" android:textColor="#ffffff" android:textSize="15sp" />
当然也可以这样设置他的android:maxLines="2"
来限制它的高度从而实现滚动。然后通过设置TextView
的滚动实例也就是上面提到的方法setMovementMethod(new ScrollingMovementMethod())
。这样就实现了滚动了。
监听事件
当我们拖动的时候,如果文字高度超过了最大的高度我们就要整个布局都可以上滑一点,方便用户查看更多的文本内容。滑动布局使用的layout()
方法来调整布局的位置。
/** * 分发事件 * * @param event * @return */ @Override public boolean dispatchTouchEvent(MotionEvent event) { if (isFirst) { // 得到屏幕的宽 displayMetrics = getResources().getDisplayMetrics(); screenWidth = displayMetrics.widthPixels; // 得到标题栏和状态栏的高度 Rect rect = new Rect(); Window window = ((Activity) context).getWindow(); int statusBarHeight = rect.top; int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop(); int titleBarHeight = contentViewTop - statusBarHeight; // 得到屏幕的高 screenHeight = displayMetrics.heightPixels - (statusBarHeight + titleBarHeight); isFirst = false; } if(!onTouch){ return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = event.getRawX(); lastY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: //移动的距离 float distanceX = event.getRawX() - lastX; float distanceY = event.getRawY() - lastY; //移动后控件的坐标 left = (int) (getLeft() + distanceX); top = (int) (getTop() + distanceY); right = (int) (getRight() + distanceX); bottom = (int) (getBottom() + distanceY); //处理拖出屏幕的情况 if (left < 0) { left = 0; right = getWidth(); } if (right > screenWidth) { right = screenWidth; left = screenWidth - getWidth(); } if (top < 0) { top = 0; bottom = getHeight(); } if (bottom > bottombot) { bottom = bottombot; top = bottombot - getHeight(); } if (bottom < maxBottom) { bottom = maxBottom; top = maxBottom - getHeight(); } //移动View layout(getLeft(), top, getRight(), bottom); lastX = event.getRawX(); lastY = event.getRawY(); break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_CANCEL: break; } return super.dispatchTouchEvent(event); }
在MotionEvent.ACTION_DOWN
时记录用户点击的位置,注意getRawX()``getRawY()
是相对于屏幕位置坐标,getX()``getY()
是相对于容器的位置坐标。在MotionEvent.ACTION_MOVE
计算用户手指移动的距离,然后计算出容器移动后的坐标,在判断用户移动的距离是否在允许的移动范围内。最后通过layout()
方法来移动容器。
关于getLeft()
,getTop()
,getRight()
,getBottom()
下面这张图应该能很好的解释清楚了
其中比较重要的两个值是容器的最高和最低的坐标位置,这两个位置是通过内容来决定的,所以我们在设置内容的时候需要计算得到这两个坐标
/** * 设置内容 * * @param content */ public void setContent(final String content) { mIsUpdate = true; mContentView.setText(content); mTitleView.removeCallbacks(mRun); onTouch=false; //添加全局布局侦听器 getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (mIsUpdate) { mIsUpdate = false; int scHeight = mContentView.getHeight(); bottomtop = getTop(); bottomtop2=mOuterLayout.getTop(); bottombot = getBottom(); bottombot2=mOuterLayout.getBottom(); maxBottom = getBottom(); if (scHeight > pixelsToDp(context,100)) { bottomtop = getTop() + (mContentView.getHeight() - MINHRIGHT); bottombot = getBottom() + (mContentView.getHeight() - MINHRIGHT); bottomtop2 = mOuterLayout.getTop() + (mContentView.getHeight() - MINHRIGHT); bottombot2 = mOuterLayout.getBottom() + (mContentView.getHeight() - MINHRIGHT); } mOuterLayout.layout(getLeft(), bottomtop2, getRight(), bottombot2); mTitleView.postDelayed(mRun =new Runnable() { @Override public void run() { layout(getLeft(), bottomtop, getRight(), bottombot); mOuterLayout.layout(getLeft(), 0, getRight(), mOuterLayout.getHeight()); onTouch=true; } }, 800); } } }); }
Android
中需要拿到控件的宽高或者getTop()
需要在布局文件加载完成后才能拿到,所以这里添加了布局监听器,当布局完成后回调此接口拿到我们需要的值。
在ViewTreeObserver
中有许多对布局监听的方法
ViewTreeObserver.OnDrawListener
当要绘制视图树时调用ViewTreeObserver.OnGlobalFocusChangeListener
当视图树中的焦点状态改变时要调用ViewTreeObserver.OnGlobalLayoutListener
当全局布局状态或视图树中的视图的可见性更改时要调用ViewTreeObserver.OnScrollChangedListener
在视图树中的某些内容已滚动时调用ViewTreeObserver.OnWindowAttachListener
当视图层次结构附加到其窗口并从其窗口拆离时要调用
还有很多可以去官方文档查看 ViewTreeObserver
然后通过内容的高度来计算拖动时getBottom()
的最小值,也就是容器最高可以拖动哪个位置,当scHeight > pixelsToDp(context,100)
也就是内容高度大于了最大的限定高度,那么就需要重新计算容器的位置来隐藏多余的内容
请原谅一个灵魂画手。。。。
中间的粗黑线是当前容器应该在的位置,那么我们就需要把当前容器像下移一定位置隐藏部分内容, 在隐藏黑框前获取它的getBottom()
也就是黑框的最高可以滑动的位置
红框为父容器,黑色的为当前容器,可以看到隐藏实际就是把当前容器的位置下移,红线就是上面黑线的位置。 当前的位置通过bottombot = getBottom() + (mContentView.getHeight() - MINHRIGHT);
计算得到它当前的getBottom()
的位置,bottombot
也是当前容器可以滑动的最低位置了。然后在通过bottomtop = getTop() + (mContentView.getHeight() - MINHRIGHT);
计算得到了它的getTop()
位置。由于我们设置了内容的TextView
的最大高度,所以mContentView.getHeight()
获取到的最大高度如果超过了TextView
的maxHeight
就是一个固定的值,这也就固定了容器的最高滑动高度,然后通过mOuterLayout.layout(getLeft(), bottomtop2, getRight(), bottombot2);
来隐藏了部分内容,
这里还有个小问题,当换内容时容器需要计算内容的高度, 那么就会去重新计算内容控件的位置,如果立即去设置当前容器的位置会出现闪屏的bug 通过postDelayed
来延迟了隐藏当前容器多余内容,然后在最外层又添加了一个空的布局,这样闪屏用户就看不到了。 虽然解决了问题但是肯定还有更好的解决办法, 各位有什么好的方法欢迎私信交流!
使用
使用就很简单了,放在你需要的布局里面就行了,设置他的内容和标题
<com.qinanyu.bottomlayout.TestBottomLayout android:id="@+id/bottom_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" app:bot_content="wtf" app:bot_title="江西农名工带头"> </com.qinanyu.bottomlayout.TestBottomLayout>
也可以在代码中设置内容和标题
mBottomLayout.setTtitle("这是一个普通话新闻个普通话新闻个普通话新闻的标题"); mBottomLayout.setContent("zheshi yi shh hs卡号升到发货发是一个普通话新闻" + "个普通话新闻个普通话是一个普通话新闻个" + "徽我是安个普通话是一个普通话新 ");
END ~~
共同学习,写下你的评论
评论加载中...
作者其他优质文章