Android 仿网易新闻图片新闻底部布局

最近公司项目需求需要用到网易新闻图片新闻底部的滑动控件,找了一圈轮子没有知道合适的决定自己造一个轮子吧,最后实现出来的效果和网易还是有一点差别的,有什么的好的思路欢迎私信交流。

项目地址https://github.com/Hemumu/BottomDrawerLayout

先上效果图和网易新闻的对比

网易新闻

GIF.gif

我实现的

GIF22.gif

基本效果实现了,后面还需要进一步优化。实现的答题思路就是通过拦截滑动事件和调用layout()的实现控件的拖动和高度的限制,内容的高度是由TextViewmaxHeightminHeight来限制的。

初始化

新建一个类继承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());,当我们设置了TextViewmaxHeightminHeight后当字数超过了限制我们当然希望是可以滑动的,但是TextView是无法滑动的。最开始我的想法是用ScrollView嵌套一个TextView但是ScrollView没有限制高度的方法,要自己去计算最高和最低的高度很麻烦。最后在Stackoverflow上看到了解决方法,使用setMovementMethod方法,我们看看官方文档的解释

QQ截图20170314161222.png

什么?看不懂?好吧我就是放上来装逼的。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()下面这张图应该能很好的解释清楚了

QQ截图20170314205418.png

图片来自http://blog.csdn.net/u013872857/article/details/53750682

其中比较重要的两个值是容器的最高和最低的坐标位置,这两个位置是通过内容来决定的,所以我们在设置内容的时候需要计算得到这两个坐标

/**
     * 设置内容
     *
     * @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)也就是内容高度大于了最大的限定高度,那么就需要重新计算容器的位置来隐藏多余的内容

QQ截图20170314211033.png

请原谅一个灵魂画手。。。。

中间的粗黑线是当前容器应该在的位置,那么我们就需要把当前容器像下移一定位置隐藏部分内容, 在隐藏黑框前获取它的getBottom()也就是黑框的最高可以滑动的位置

QQ截图20170314211053.png

红框为父容器,黑色的为当前容器,可以看到隐藏实际就是把当前容器的位置下移,红线就是上面黑线的位置。 当前的位置通过bottombot = getBottom() + (mContentView.getHeight() - MINHRIGHT);计算得到它当前的getBottom()的位置,bottombot也是当前容器可以滑动的最低位置了。然后在通过bottomtop = getTop() + (mContentView.getHeight() - MINHRIGHT);计算得到了它的getTop()位置。由于我们设置了内容的TextView的最大高度,所以mContentView.getHeight()获取到的最大高度如果超过了TextViewmaxHeight就是一个固定的值,这也就固定了容器的最高滑动高度,然后通过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 ~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容