仿QQ拖拽界面效果(侧滑面板),我们一般继承Layout,不会直接去继承ViewGroup,而是继承FrameLayout,为什么五大布局我们偏偏只继承FrameLayout呢?

  • 第一,FrameLayout继承ViewPager;

  • 第二,其他四大布局比FrameLayout多做了onDraw,onLayout,FrameLayout只有层级上下关系,没有位置的相对关系,而我们自定义控制对位置的相对关系是自定义的,不需要父类一开始就给我们定好,只需要父类给咱们测量控件的宽高即可。

如此这般,FrameLayout是最好的选择!

具体实现:

  • 创建一个类继承FrameLayout类,覆写其构造函数。
public DragLayout(Context context) {this(context, null);
}public DragLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);
}public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
}
  • 初始化操作(通过静态方法),初始化ViewDragHelper对象:Google2013年IO大会提出的,解决界面控件拖拽移动问题。(v4包下)
/**
* ViewGroup forParent:所要拖拽孩子的父View
* float sensitivity:敏感度
* Callback cb:回调接口,当你触摸到子View的时候就会响应
* mTouchSlop:最小敏感范围,值越小越敏感
*
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb) {ViewDragHelper helper = create(forParent, cb);helper.mTouchSlop = (int)((float)helper.mTouchSlop * (1.0F / sensitivity));return helper;
}
*/ViewDragHelper mDragHelper = ViewDragHelper.create(this, mCallback);
  • 传递触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {//传递给mDragHelperreturn mDragHelper.shouldInterceptTouchEvent(ev);
}@Override
public boolean onTouchEvent(MotionEvent event) {try {mDragHelper.processTouchEvent(event);}catch (Exception e) {}//返回true,持续接收事件return true;
}
  • 重写拖拽回调方法
ViewDragHelper.Callback mCallback =  new ViewDragHelper.Callback() {/*1.根据返回结果决定当前child是否可以拖拽child:当前被拖拽的ViewpointerId:区分多点触摸的id*/@Overridepublic boolean tryCaptureView(View child, int pointerId) {//直接return true,说明布局中的mLeftContent和mMainContent俩界面都能拖拽return child == mMainContent;}/*2.根据建议值修正将要移动到的(横向)位置*/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {return left;}
};
  • 获得布局中的子View
/*** 当xml填充结束之后,此方法被调用,同时它的所有的孩子都添加进来了*/
@Override
protected void onFinishInflate() {super.onFinishInflate();//Github//写注释//容错性检查(至少有俩子View,子View必须是ViewGroup的子类)if(getChildCount() < 2) {throw new IllegalStateException("Your ViewGroup must have two children at least!");}if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {throw new IllegalArgumentException("Your children must be an instance of ViewGroup!");}mLeftContent = (ViewGroup) getChildAt(0);//根据索引找孩子mMainContent = (ViewGroup) getChildAt(1);//根据索引找孩子
}
  • 布局文件
<com.zanelove.androidcustomdemo.drag.DragLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/bg"><LinearLayoutandroid:background="#66ff0000"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="#6600ff00"android:orientation="horizontal"/></com.zanelove.androidcustomdemo.drag.DragLayout>
  • 效果图

以上,只是简单地演示一下效果!接下来,才是真正的开始!

  • 详解拖拽回调事件中的方法
ViewDragHelper.Callback mCallback =  new ViewDragHelper.Callback() {/*** 1.根据返回结果决定当前child是否可以拖拽child:当前被拖拽的ViewpointerId:区分多点触摸的id* @param child* @param pointerId* @return*/@Overridepublic boolean tryCaptureView(View child, int pointerId) {Log.e(TAG,"tryCaptureView"+child);return child == mMainContent;}/*** 当capturedChild被捕获时,回调此方法* @param capturedChild* @param activePointerId*/@Overridepublic void onViewCaptured(View capturedChild, int activePointerId) {Log.e(TAG,"onViewCaptured"+capturedChild);super.onViewCaptured(capturedChild, activePointerId);}@Overridepublic int getViewHorizontalDragRange(View child) {return super.getViewHorizontalDragRange(child);}/*** 2.根据建议值修正将要移动到的(横向)位置* @param child* @param left* @param dx* @return*/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {return left;}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {return super.clampViewPositionVertical(child, top, dy);}@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);}@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {super.onViewReleased(releasedChild, xvel, yvel);}
};

代码跟上来的朋友也行已经发现了,限制咱们的界面可以无限制的拖拽着,那么,我只想让红色界面只能拖拽整个屏幕的60%,那这个问题,我们该如何解决呢?在这个问题之前,我们还有一个问题,咱们的屏幕的宽度如何拿到呢?

问题一:咱们的屏幕的宽高度如何拿到呢?屏幕的60%如何获得呢?
解决方法:
我们第一反应就是这个测量的宽高度问题,应该放在onMeasure()方法中调用getMeasureWidth() or getMeasureHeight()方法来进行解决,那么,现在我们其实可以重写onSizeChanged()方法来解决获得宽高度的问题!

这个onSizeChanged()方法,也是在onMeasure()方法之后,但何时会被调用,取决于onMeasure()测量前后发现尺寸发生变化之后,才会去调用此方法。

/*** 当onMeasure方法前后测量尺寸有变化的时候回调此方法* @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);measureHeight = getMeasuredHeight(); //获得屏幕的高度measureWidth = getMeasuredWidth(); //获得屏幕的宽度mRange = (int) (measureWidth * 0.6f); //获得屏幕的60%
}

问题二:拿到了值之后我们只想让红色界面只能拖拽整个屏幕的60%?
解决方法:

/*** 2.根据建议值修正将要移动到的(横向)位置* @param child 当前拖拽的View* @param left 新的位置的建议值  left = oldLeft = dx;* @param dx 位置变化量* @return*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {Log.e(TAG,"clampViewPositionHorizontal:"+" oldLeft"+child.getLeft() + " dx:"+dx+" left"+left);if(child == mMainContent) {left = fixLeft(left);}return left;
}/*** 根据范围修正左边的值* @param left* @return*/
private int fixLeft(int left) {if(left < 0) {return 0;}else if(left > mRange) {return mRange;}return left;
}

以上问题解决了,还有以下问题:拖拽左界面(左边红色区域)如同拖拽主界面(绿色区域),意思就是拖拽左界面产生的值以及所有的事件全部交给主界面来处理!

/*** 3.当View位置改变的时候,处理要做的事情(更新状态,伴随动画,重绘界面),注意此时的View已经发生了位置的改变* @param changedView 改变位置的View* @param left 新的左边值* @param top* @param dx 水平方向变化量* @param dy*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);int newLeft = left;if(changedView == mLeftContent) {//把当前变化量传递给mMainContentnewLeft = mMainContent.getLeft() + dx;//进行修正newLeft = fixLeft(newLeft);//当左面板移动之后,再强制放回去mLeftContent.layout(0,0,0+measureWidth,0+measureHeight);mMainContent.layout(newLeft, 0, newLeft + measureWidth, 0 + measureHeight);}//为了兼容低版本,每次修改之后重绘界面invalidate();
}
  • 效果图:

到目前为止,拖拽先说到这里,那么接下来我们来说说动画,当我们拖拽界面到一定的位置的时候,我放手,那么被拖拽的界面就得找‘就近原则’了,然后使用动画回到应该到的位置!

那这个‘就近原则’如何确定?

  • 代码:
    关闭动画(主界面往左走):
/*** 当View被释放的时候,处理的事情(执行动画)* @param releasedChild 被释放的子View* @param xvel 水平方向的速度(右拖拽为正,左拖拽为负)* @param yvel 垂直方向的速度(上拖拽为负,下拖拽为正)**/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {Log.d(TAG,"onViewReleased:"+" xvel"+xvel+" yvel:" + yvel);super.onViewReleased(releasedChild, xvel, yvel);//判断执行 关闭/开启//先考虑所有开启的情况,剩下的就都是关闭的情况if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){open();}else if(xvel > 0) {open();}else{close();}
}/*** 关闭*/
private void close() {close(true);
}/*** 关闭时是否平滑* @param isSmooth true 平滑*                 false 不平滑*/
private void close(boolean isSmooth) {int finalLeft = 0;if(isSmooth) {//1.触发一个平滑动画if(mDragHelper.smoothSlideViewTo(mMainContent,finalLeft,0)){//返回true代表还没有移动到指定位置,需要刷新界面ViewCompat.postInvalidateOnAnimation(this); //参数传this(child所在的ViewGroup)}}else {mMainContent.layout(finalLeft, 0, finalLeft + measureWidth, 0 + measureHeight);}
}@Override
public void computeScroll() {super.computeScroll();//2.持续平滑动画(高频率调用)if(mDragHelper.continueSettling(true)) {//如果返回true,还需要继续执行ViewCompat.postInvalidateOnAnimation(this); //参数传this(child所在的ViewGroup)}}

开启动画(主界面往右走):

/*** 开启*/
private void open() {open(true);
}/*** 开启时是否平滑* @param isSmooth true 平滑*                 false 不平滑*/
private void open(boolean isSmooth) {int finalLeft = mRange;if(isSmooth) {//1.触发一个平滑动画if(mDragHelper.smoothSlideViewTo(mMainContent,finalLeft,0)){//返回true代表还没有移动到指定位置,需要刷新界面ViewCompat.postInvalidateOnAnimation(this); //参数传this(child所在的ViewGroup)}}else {mMainContent.layout(finalLeft,0,finalLeft + measureWidth,0 + measureHeight);}
}

效果图,我就不贴了!还是你自己运行在自己的真机上或者模拟器上体验体验吧!

好了,到目前位置,大概的一个框架我们搭建好了,也许好多人会问我,说好的仿QQ界面的呢?咋就给我整个了这逼玩意儿!

那好,现在就给大伙分析分析QQ界面有哪些是我们要效仿的:

  • 伴随动画:

    1. 左面板:缩放动画,平移动画,透明度动画
    2. 主界面:缩放动画
    3. 背景动画:亮度变化(颜色变化)
/*** 3.当View位置改变的时候,处理要做的事情(更新状态,伴随动画,重绘界面),注意此时的View已经发生了位置的改变* @param changedView 改变位置的View* @param left 新的左边值* @param top* @param dx 水平方向变化量  (右拖拽为正,左拖拽为负)* @param dy*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);int newLeft = left;if(changedView == mLeftContent) {//把当前变化量传递给mMainContentnewLeft = mMainContent.getLeft() + dx;//进行修正newLeft = fixLeft(newLeft);//当左面板移动之后,再强制放回去mLeftContent.layout(0,0,0+measureWidth,0+measureHeight);mMainContent.layout(newLeft, 0, newLeft + measureWidth, 0 + measureHeight);}//更新状态,执行动画dispatchDragEvent(newLeft);//为了兼容低版本,每次修改之后重绘界面invalidate();
}private void dispatchDragEvent(int newLeft) {/*** Github* Jake Wharton* nineoldandroids.jar 属性动画兼容低版本* ActionBarSherlock 状态栏*/float percent = newLeft * 1.0f / mRange;
//        伴随动画:
//        1. 左面板:缩放动画,平移动画,透明度动画/*3.0以上版本才兼容mLeftContent.setScaleX(0.5f + 0.5f * percent);mLeftContent.setScaleY(0.5f + 0.5f * percent);*///需要导入nineoldandroids.jar包/*** 缩放动画*/ViewHelper.setScaleX(mLeftContent,evaluate(percent,0.5f,1.0f));ViewHelper.setScaleY(mLeftContent,0.5f + 0.5f * percent);/*** 平移动画 -mWidth / 2.0f -> 0.0f*/ViewHelper.setTranslationX(mLeftContent,evaluate(percent,-measureWidth / 2.0f,0));/*** 透明度*/ViewHelper.setAlpha(mLeftContent,evaluate(percent,0.5f,1.0f));
//        2. 主界面:缩放动画ViewHelper.setScaleX(mMainContent,evaluate(percent,1.0f,0.8f));ViewHelper.setScaleY(mMainContent,evaluate(percent,1.0f,0.8f));
//        3. 背景动画:亮度变化(颜色变化)getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK,Color.TRANSPARENT),PorterDuff.Mode.SRC_OVER);
}public Float evaluate(float fraction,Number startValue,Number endValue){float startFloat = startValue.floatValue();return startFloat + fraction * (endValue.floatValue() - startFloat);
}/*** 颜色变化过度* @param fraction* @param startValue* @param endValue* @return*/
public Object evaluateColor(float fraction, Object startValue, Object endValue) {int startInt = (Integer) startValue;int startA = (startInt >> 24) & 0xff;int startR = (startInt >> 16) & 0xff;int startG = (startInt >> 8) & 0xff;int startB = startInt & 0xff;int endInt = (Integer) endValue;int endA = (endInt >> 24) & 0xff;int endR = (endInt >> 16) & 0xff;int endG = (endInt >> 8) & 0xff;int endB = endInt & 0xff;return (int)((startA + (int)(fraction * (endA - startA))) << 24) |(int)((startR + (int)(fraction * (endR - startR))) << 16) |(int)((startG + (int)(fraction * (endG - startG))) << 8) |(int)((startB + (int)(fraction * (endB - startB))));
}
  • 状态监听
private OnDragStatusChangeListener mListener;
//初始状态
private Status mStatus = Status.Close;
/*** 状态枚举*/
public static enum Status{Close,Open,Draging;
}public interface OnDragStatusChangeListener {void onClose();void onOpen();void onDraging(float percent);
}public void setDragStatusListener(OnDragStatusChangeListener onDragStatusChangeListener){this.mListener = onDragStatusChangeListener;
}private void dispatchDragEvent(int newLeft) {float percent = newLeft * 1.0f / mRange;//更新状态,执行回调Status perStatus = mStatus; //上一次状态mStatus = updateStatus(percent);if(mStatus != perStatus) {//状态发生变化if(mStatus == Status.Close) {//当前变为关闭状态if(mListener != null) {mListener.onClose();}}else if(mStatus == Status.Open) {if(mListener != null) {mListener.onOpen();}}}//每时每刻都在调用onDraging()if(mListener != null) {mListener.onDraging(percent);}/*** 伴随动画:*/animViews(percent);
}

在MainActivity类中:

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final ListView mLeftList = (ListView) findViewById(R.id.lv_left);ListView mMainList = (ListView) findViewById(R.id.lv_main);final ImageView iv_head = (ImageView) findViewById(R.id.iv_head);//查找DragLayout,设置监听DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);mDragLayout.setDragStatusListener(new DragLayout.OnDragStatusChangeListener() {@Overridepublic void onClose() {Util.showToast(MainActivity.this,"onClose");//让图标晃动ObjectAnimator mAnim = ObjectAnimator.ofFloat(iv_head, "translationX", 15.0f);mAnim.setInterpolator(new CycleInterpolator(4));//差值器  来回晃动4圈mAnim.setDuration(800);mAnim.start();}@Overridepublic void onOpen() {Util.showToast(MainActivity.this,"onOpen");//验证回调方法:左面板ListView随机设置一个条目Random random = new Random();int nextInt = random.nextInt(50);mLeftList.smoothScrollToPosition(nextInt);}@Overridepublic void onDraging(float percent) {//更新图标的透明度//1.0 -> 0.0ViewHelper.setAlpha(iv_head,1 - percent);}});mLeftList.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = super.getView(position, convertView, parent);TextView mTextView = (TextView)view;mTextView.setTextColor(Color.WHITE);return view;}});mMainList.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, Cheeses.NAMES){@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = super.getView(position, convertView, parent);TextView mTextView = (TextView)view;mTextView.setTextColor(Color.BLACK);return view;}});
}

效果图:

细心的朋友也行看到了,现在的界面布局都不一样了,哈哈!关于布局我就不一一粘贴复制了,你们等会直接下载我的Demo看看就OK了。

为了做到高仿QQ拖拽界面效果,那么现在就明摆着一个细节就是:被拖拽的主界面的ListView是不能滚动的,而我们的却滚动了!这可如何是好呀!

  • 触摸优化,重写ViewGroup;当左界面处于Draging或Open状态时,主界面的ListView事件应该禁用!

    • 自定义LinearLayout
    • 重写OnInterceptionTouchEvent()和onTouchEvent()
public class DragLinearLayout extends LinearLayout {private DragLayout mDragLayout;public DragLinearLayout(Context context) {super(context);}public DragLinearLayout(Context context, AttributeSet attrs) {super(context, attrs);}public void setDragLayout(DragLayout mDragLayout){this.mDragLayout = mDragLayout;}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {//如果当前是关闭状态,子View:ListView能滚动if(mDragLayout.getStatus() == DragLayout.Status.Close) {return super.onInterceptTouchEvent(ev); //false,不拦截事件,由子View来处理}else {return true; //拦截事件,交给DragLinearLayout来处理}}@Overridepublic boolean onTouchEvent(MotionEvent event) {//如果当前是关闭状态,子View:ListView能滚动if(mDragLayout.getStatus() == DragLayout.Status.Close) {return super.onTouchEvent(event); //false,不拦截事件,由子View来处理}else {//手指抬起,执行关闭操作if(event.getAction() == MotionEvent.ACTION_UP) {mDragLayout.close();}return true; //拦截事件,交给DragLinearLayout来处理}}
}

示例代码戳Here

android开发之仿QQ拖拽界面效果(侧滑面板)相关推荐

  1. Android开发之仿QQ表情实现(上)

    大家晚上好,,小鹿又来了..最近小鹿特别忙,忙到没时间发表博客了(注:以下内容过于简单请大家不要喷,仅提供初学者学习) 今天发表两篇文章,分别是讲解模拟QQ表情的实现,先给大家看效果图,,,, 开始了 ...

  2. Android qq消息气泡实现效果,Android 实现仿QQ拖拽气泡效果的示例

    效果图: 一.实现思路 在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN.MOVE.UP事件中分别处理拖拽效 ...

  3. Android开发之仿QQ表情实现(下)

    大家中午好,,,,,,小鹿吃草刚回来真是不好意思,,,, 上篇文章已经讲到GirdView的使用,本节内容是基于上篇内容实现更完美的QQ表情的实现,具体的说,本节内容实现的QQ表情是使用了GirdVi ...

  4. Android 贝塞尔曲线实现QQ拖拽清除效果

    纯属好奇心驱动写的一个学习性Demo,效果如下: 这个小功能最重要的点在于起始点和触摸点之间的连接线绘制,它并不是一条单纯的直线,而是中间细两头粗的一条不规则的Path,而这个中间向内弯曲的效果正是一 ...

  5. Android仿Ios下拉回弹,Android ReboundScrollView仿IOS拖拽回弹效果

    初衷: 其实github上有很多这种ScrollView的项目,但是不得不说功能太多太乱了,我就只是想要一个简单效果的ScrollView,另外监听下滑动距离而已,想想还是自己写了个. 这里先说下思路 ...

  6. 手把手实现腾讯qq拖拽删去效果(二)

    这节,就一个任务如何把上节自定义的翻页动画控件整进下拉列表中去. 由于是自定义的下拉列表控件,我们需要自定义能够上啦下滑的listview,这势必会造成这个问题,上拉刷新要响应相应touch事件,拖拽 ...

  7. 安卓开发之仿QQ界面

    安卓开发之仿腾讯QQ 1.系统功能说明及软件界面展示 2.软件界面展示 3.数据流图(我瞎画的) 4.操作流程图(也是我瞎画的) 1.系统功能说明及软件界面展示 此APP是模仿的移动端的腾讯QQ.众所 ...

  8. Android开发之仿360手机卫士悬浮窗效果

    基本的实现原理,这种桌面悬浮窗的效果很类似与Widget,但是它比Widget要灵活的多.主要是通过WindowManager这个类来实现的,调用这个类的addView方法用于添加一个悬浮窗,upda ...

  9. android qq消息数 拖拽动画,史上最详细仿QQ未读消息拖拽粘性效果的实现

    好久没写文章了,前段时间由于项目代码重构忙了一段时间,现在终于有点时间了就为大家带来一篇关于动画学习的自定义View:类似QQ消息拖拽的效果. 其实QQ当时更新的时候我还没注意到这个小红点是可以拖拽的 ...

最新文章

  1. 理解Meta Learning 元学习,这篇文章就够了!
  2. chrome 请求带上cookie_【编号0002】请求头的内容,及其相关知识铺垫
  3. 北大女生拿下阿里数学预赛第一名!决赛入围率不到1%,最小晋级选手只有14岁...
  4. spring beans源码解读之--Bean的定义及包装
  5. jar 反编译_Java加密jar包流程
  6. python字符串是什么_python字符串表示什么
  7. SQL Server 2000/2005 数据库分页
  8. 乐乐茶签约帆软软件,打造新式茶饮数字化管理新标杆
  9. java的字符_Java中的字符
  10. Android蓝牙自动配对工具类,亲测好使!!!
  11. Java求抛物线输入角度速度_知道初速度和抛物线的角度,怎么计算落点
  12. WHYZOJ-#116[NOIP模拟] czy把妹(区间DP)
  13. qduoj 分辣条1 (搜索+剪枝)
  14. Python读取图像数据的常用方法
  15. DL1 - Neural Networks and Deep Learning
  16. yii2 aliases web.php,别名(Aliases) - Yii2 权威指南
  17. 程序猿真的觉得写代码比女朋友重要吗?
  18. 史上最全的CDN内容分发网络实战技巧
  19. B70极路由4增强版安装frps
  20. C语言实现俄罗斯方块游戏

热门文章

  1. 学校计算机及网络保密总结,学校保密工作总结范文
  2. vs2010c语言小游戏,用C语言写一个三子棋小游戏(用VS2010实现,加强版)
  3. 华云数据荣获2021中国软件和信息服务业信创实力企业及社会责任贡献企业双项大奖
  4. CNN和Transformer相结合的模型
  5. 异地过年,我要用它看春晚!- Qt趣味开发之基于QtAV的电视播放器
  6. 商城管理系统服务器,基于Python实现的购物商城管理系统
  7. Vue3:基础项目UI框架PC端(Element ui,view-ui-plus,Ant Design Vue)
  8. 加入百度移动联盟广告SSP美图android安卓源码 详情有演示apk
  9. 边缘计算智慧灯杆网关——数据传输的桥梁
  10. html手机截屏保存,手机屏幕截图无法保存该怎么解决?