Android开发之QQ侧滑面板

本文探讨了如何在Android应用中创建类似QQ的侧滑面板,详细介绍了开发过程和技术要点,帮助开发者实现这一常见交互效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android开发之QQ侧滑面板

前段时间,腾讯手机QQ新版一推出,其QQ侧滑面板的炫酷效果让诸多的开发人员眼前一亮,这么炫酷的效果该怎么实现呢?本人稍稍研究了下,发现还算不是太难,基本效果差不多,下面贴出代码,望各位大神批评指正!

先效果图:


主要代码如下:
MainActivity
package com.hxht.testqqslidingpanel;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.CycleInterpolator;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.hxht.testqqslidingpanel.customdragview.SlidingPanelDragView;
import com.hxht.testqqslidingpanel.mylinerlayout.MyLinerlayout;
import com.nineoldandroids.view.ViewHelper;

import java.util.Random;


public class MainActivity extends Activity {

    private LinearLayout ll_leftview;
    private ImageView iv_left_icon;
    private ListView lv_left;
    private MyLinerlayout ll_mainview;
    private ImageView iv_main_icon;
    private TextView tv_main;
    private ListView lv_main;
    private static final String[] names = new String[]{
            "宋江",
            "卢俊义",
            "吴用",
            "公孙胜",
            "关胜",
            "林冲",
            "秦明",
            "呼延灼",
            "花荣",
            "柴进",
            "李应",
            "朱仝",
            "鲁智深",
            "武松",
            "董平",
            "张清",
            "杨志",
            "徐宁",
            "索超",
            "戴宗",
            "刘唐",
            "李逵",
            "史进",
            "穆弘",
            "雷横",
            "李俊",
            "阮小二",
            "张横",
            "阮小五",
            "张顺",
            "阮小七",
            "杨雄",
            "石秀",
            "解珍",
            "解宝 ",
            "燕青",
            "朱武",
            "黄信",
            "孙立",
            "宣赞",
            "郝思文",
            "韩滔",
            "彭玘",
            "单廷圭",
            "魏定国",
            "萧让",
            "裴宣",
            "欧鹏",
            "邓飞",
            "燕顺",
            "杨林",
            "凌振",
            "蒋敬",
            "吕方",
            "郭盛",
            "安道全",
            "皇甫端",
            "王英",
            "扈三娘",
            "鲍旭",
            "樊瑞",
            "孔明",
            "孔亮",
            "项充",
            "李衮",
            "金大坚",
            "马麟",
            "童威",
            "童猛",
            "孟康",
            "侯健",
            "陈达",
            "杨春",
            "郑天寿",
            "陶宗旺",
            "宋清",
            "乐和",
            "龚旺",
            "丁得孙",
            "穆春",
            "曹正",
            "宋万",
            "杜迁",
            "薛永",
            "李忠",
            "周通",
            "汤隆",
            "杜兴",
            "邹渊",
            "邹润",
            "朱贵",
            "朱富",
            "施恩",
            "蔡福",
            "蔡庆",
            "李立",
            "李云",
            "焦挺",
            "石勇",
            "孙新",
            "顾大嫂",
            "孙二娘",
            "王定六",
            "郁保四",
            "白胜",
            "时迁",
            "段景住"
    };
    private SlidingPanelDragView dragView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initViews();

        initData();
    }

    private void initData() {

        ll_mainview.setDragView(dragView);

        lv_left.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, names) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView tv = (TextView) super.getView(position, convertView, parent);
                tv.setTextColor(Color.WHITE);
                return tv;
            }
        });

        lv_main.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, names) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView tv = (TextView) super.getView(position, convertView, parent);
                tv.setTextColor(Color.WHITE);
                return tv;
            }
        });

        dragView.setOnUpdateStatusListener(new SlidingPanelDragView.OnUpdateStatusListener() {

            @Override
            public void onOpen() {
                //Toast.makeText(MainActivity.this, "打开了侧滑面板", Toast.LENGTH_SHORT).show();
                Random random = new Random();
                int index = random.nextInt(100);
                System.out.println(index);
                lv_left.smoothScrollToPosition(index);
            }

            @Override
            public void onClose() {
                //Toast.makeText(MainActivity.this, "关闭了侧滑面板", Toast.LENGTH_SHORT).show();
                ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(iv_main_icon, "translationX", 15f);
                objectAnimator.setInterpolator(new CycleInterpolator(4));
                objectAnimator.setDuration(200);
                objectAnimator.start();
            }

            @Override
            public void onDraging(float percent) {
                ViewHelper.setAlpha(iv_left_icon, percent);
                ViewHelper.setAlpha(iv_main_icon, 1 - percent);
                ViewHelper.setScaleX(tv_main, 1 - percent);
                ViewHelper.setScaleY(tv_main, 1 - percent);
            }
        });
    }

    private void initViews() {
        ll_leftview = (LinearLayout) findViewById(R.id.ll_leftview);
        iv_left_icon = (ImageView) findViewById(R.id.iv_left_icon);
        lv_left = (ListView) findViewById(R.id.lv_left);
        ll_mainview = (MyLinerlayout) findViewById(R.id.ll_mainview);
        iv_main_icon = (ImageView) findViewById(R.id.iv_main_icon);
        tv_main = (TextView) findViewById(R.id.tv_main);
        lv_main = (ListView) findViewById(R.id.lv_main);
        dragView = (SlidingPanelDragView) findViewById(R.id.slidingpaneldragview);
    }
}

SlidingPanelDragView
package com.hxht.testqqslidingpanel.customdragview;

import android.animation.ArgbEvaluator;
import android.animation.FloatEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.nineoldandroids.view.ViewHelper;

public class SlidingPanelDragView extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private ViewGroup leftView;
    private ViewGroup mainView;
    private int mWidth;
    private int mHeight;
    private int range;

    public enum Status {
        OPEN,
        CLOSE,
        DRAGING
    }

    public interface OnUpdateStatusListener {
        void onOpen();

        void onClose();

        void onDraging(float percent);
    }

    private Status status = Status.CLOSE;

    private OnUpdateStatusListener onUpdateStatusListener;

    public OnUpdateStatusListener getOnUpdateStatusListener() {
        return onUpdateStatusListener;
    }

    public void setOnUpdateStatusListener(OnUpdateStatusListener onUpdateStatusListener) {
        this.onUpdateStatusListener = onUpdateStatusListener;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

        //第三步:处理监听

        /**
         * 试图去捕捉的View
         * 返回true则表示该控件的所有子控件都能被聚焦
         * 返回false则表示该控件的所有子控件都不能被聚焦
         * @param child
         * @param pointerId
         * @return
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {

            if (child == mainView) {
                left = fixLeft(left);
            }

            return left;
        }


        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            int newLeft = mainView.getLeft();
            if (changedView == mainView) {
                newLeft = left;
            } else {
                newLeft += dx;
            }

            newLeft = fixLeft(newLeft);

            if (changedView == leftView) {
                leftView.layout(0, 0, mWidth, mHeight);
                mainView.layout(newLeft, 0, newLeft + mWidth, mHeight);
            }

            dispatchDragEvent(newLeft);

            invalidate();//为了兼容低版本,在此处需要重绘
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);

            if (mainView.getLeft() > range / 2 && xvel == 0) {
                open();
            } else if (xvel > 0) {
                open();
            } else {
                close();
            }
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return super.getViewHorizontalDragRange(child);
        }

        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
        }

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
        }
    };

    /**
     * 更新状态
     * @param newLeft
     */
    private void dispatchDragEvent(int newLeft) {
        //在位置变化的同时,执行伴随动画等等
        float percent = newLeft * 1.0f / range;

        animViews(percent);

        if (onUpdateStatusListener != null ){
            onUpdateStatusListener.onDraging(percent);
        }

        Status lastStatus = status ;
        status = updateStatus(percent);

        if (lastStatus != status){
            if (onUpdateStatusListener != null){
                if (status == Status.CLOSE){
                    onUpdateStatusListener.onClose();
                }else if (status == Status.OPEN){
                    onUpdateStatusListener.onOpen();
                }
            }
        }
    }

    /**
     * 更新状态
     * @param percent
     * @return
     */
    private Status updateStatus(float percent) {
        if (percent == 0){
            return Status.CLOSE;
        }else if (percent == 1){
            return Status.OPEN;
        }else{
            return Status.DRAGING;
        }
    }

    private void animViews(float percent) {
        //percent的变化范围为 0.1 ---> 1.0
        //在位置变化时让控件做伴随动画

        //首先让主控件随着percent做缩放动画即从1.0--->0.8
        //float mainPercent = 0.8f + (1 - percent) * 0.2f ;
        //float leftPercent = 0.5f + (1 - percent) * 0.5f ;
        //第一种方式:
        //mainView.setScaleX(mainPercent);
        //mainView.setScaleY(mainPercent);
        //leftView.setScaleX(leftPercent);
        //leftView.setScaleY(leftPercent);

        //第二种写法,适用类型估值器
        FloatEvaluator floatEvaluator = new FloatEvaluator();
        Float mainPercent = floatEvaluator.evaluate(percent, 1.0f, 0.8f);
        Float leftPercent = floatEvaluator.evaluate(percent, 0.5f, 1.0f);
        Float leftTranslationPercent = floatEvaluator.evaluate(percent, -mWidth / 2f, 0);
        ViewHelper.setScaleX(mainView, mainPercent);
        ViewHelper.setScaleY(mainView, mainPercent);
        ViewHelper.setScaleX(leftView, leftPercent);
        ViewHelper.setScaleY(leftView, leftPercent);
        ViewHelper.setTranslationX(leftView, leftTranslationPercent);

        ArgbEvaluator argbEvaluator = new ArgbEvaluator();
        Integer color = (Integer) argbEvaluator.evaluate(percent, Color.BLACK, Color.TRANSPARENT);
        getBackground().setColorFilter(color, PorterDuff.Mode.SRC_OVER);
    }

    private int finalLeft;

    /**
     * postInvalidateOnAnimation催动执行,重绘
     */
    @Override
    public void computeScroll() {
        super.computeScroll();

        //触发一个平滑的滚动事件,若返回true则表示没有滚动到指定位置,则需要重绘
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * 定义关闭侧滑面板的方法
     */
    public void close() {
        toSmoothClose(true);
    }

    /**
     * 定义方法去平滑的关闭侧滑面板
     *
     * @param isSmooth
     */
    private void toSmoothClose(boolean isSmooth) {
        finalLeft = 0;

        toSmoothOpenOrClose(isSmooth);
    }

    /**
     * 定义打开侧滑面板的方法
     */
    private void open() {
        toSmoothOpen(true);
    }

    /**
     * 定义方法去平滑的打开侧滑面板
     *
     * @param isSmooth
     */
    private void toSmoothOpen(boolean isSmooth) {
        finalLeft = range;

        toSmoothOpenOrClose(isSmooth);
    }

    /**
     * 定义方法平滑的关闭或打开侧滑面板
     *
     * @param isSmooth
     */
    private void toSmoothOpenOrClose(boolean isSmooth) {
        if (isSmooth) {
            //触发一个平滑的滚动事件,若返回true则表示没有滚动到指定位置,则需要重绘
            if (mViewDragHelper.smoothSlideViewTo(mainView, finalLeft, 0)) {
                //引发重绘
                //第一种写法:
                //invalidate();

                //第二种写法:
                //该方法调用computeScroll方法的执行
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mainView.layout(finalLeft, 0, finalLeft + mWidth, mHeight);
        }
    }

    /**
     * 修正Left的值
     *
     * @param left
     * @return
     */
    private int fixLeft(int left) {
        if (left < 0) {
            return 0;
        } else if (left > range) {
            return range;
        } else {
            return left;
        }
    }

    public SlidingPanelDragView(Context context) {
        this(context, null);
    }

    public SlidingPanelDragView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingPanelDragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //第一步:初始化ViewDragHelper对象,由于ViewDragHelper的构造方法被私有化,所以采用create方法实例化对象
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    //第二步:托管触摸事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            mViewDragHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * xml加载完成后调用此方法,故可在此方法中拿到该控件的子View
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        if (getChildCount() < 2) {
            throw new IllegalArgumentException("This view must hava two children at least!");
        }

        if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException("The children must be instanceof ViewGroup");
        }

        leftView = (ViewGroup) getChildAt(0);
        mainView = (ViewGroup) getChildAt(1);
    }

    /**
     * 该方法的执行会催动onMesture方法的执行,故可在此方法中拿到控件的高度和宽度
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();

        range = (int) (mWidth * 0.6f);
    }
}

MyLinerlayout
package com.hxht.testqqslidingpanel.mylinerlayout;


import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

import com.hxht.testqqslidingpanel.customdragview.SlidingPanelDragView;

public class MyLinerlayout extends LinearLayout {

    private SlidingPanelDragView dragView ;

    public MyLinerlayout(Context context) {
        super(context);
    }

    public MyLinerlayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinerlayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setDragView(SlidingPanelDragView dragView) {
        this.dragView = dragView;
    }

    /**
     * 是否拦截事件
     * 返回true则拦截
     * 返回false则不拦截
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (dragView.getStatus() == SlidingPanelDragView.Status.CLOSE){
            return super.onInterceptTouchEvent(ev);
        }else{
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (dragView.getStatus() == SlidingPanelDragView.Status.CLOSE){
            return super.onTouchEvent(event);
        }else{
            //if (event.getAction() == MotionEvent.ACTION_UP){
            //    dragView.close();
            //}
            if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP){
                dragView.close();
            }
            return true;
        }
    }
}

代码已贴出,正所谓取之于社会,回报与社会,望各路大神批评指正,不喜勿喷,非常感谢!





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值