Android ViewGroup事件分发机制

来源:互联网 发布:手机excel求和软件 编辑:程序博客网 时间:2024/06/05 07:35

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39102591,本文出自【张鸿洋的博客】

上一篇已经完整的解析了Android View的事件分发机制,今天给大家代码ViewGroup事件分发的源码解析~~凡是自定义ViewGroup实现各种滑动效果的,不可避免的会出现很多事件的冲突,对ViewGroup事件分发机制的了解,也有益于大家了解冲突产生的原因,以及对冲突进行处理~

1、案例

首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout:

[java] view plain copy
  1. package com.example.zhy_event03;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.view.MotionEvent;  
  7. import android.widget.LinearLayout;  
  8.   
  9. public class MyLinearLayout extends LinearLayout  
  10. {  
  11.     private static final String TAG = MyLinearLayout.class.getSimpleName();  
  12.   
  13.     public MyLinearLayout(Context context, AttributeSet attrs)  
  14.     {  
  15.         super(context, attrs);  
  16.     }  
  17.   
  18.     @Override  
  19.     public boolean dispatchTouchEvent(MotionEvent ev)  
  20.     {  
  21.         int action = ev.getAction();  
  22.         switch (action)  
  23.         {  
  24.         case MotionEvent.ACTION_DOWN:  
  25.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
  26.             break;  
  27.         case MotionEvent.ACTION_MOVE:  
  28.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
  29.             break;  
  30.         case MotionEvent.ACTION_UP:  
  31.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
  32.             break;  
  33.   
  34.         default:  
  35.             break;  
  36.         }  
  37.         return super.dispatchTouchEvent(ev);  
  38.     }  
  39.   
  40.     @Override  
  41.     public boolean onTouchEvent(MotionEvent event)  
  42.     {  
  43.   
  44.         int action = event.getAction();  
  45.   
  46.         switch (action)  
  47.         {  
  48.         case MotionEvent.ACTION_DOWN:  
  49.             Log.e(TAG, "onTouchEvent ACTION_DOWN");  
  50.             break;  
  51.         case MotionEvent.ACTION_MOVE:  
  52.             Log.e(TAG, "onTouchEvent ACTION_MOVE");  
  53.             break;  
  54.         case MotionEvent.ACTION_UP:  
  55.             Log.e(TAG, "onTouchEvent ACTION_UP");  
  56.             break;  
  57.   
  58.         default:  
  59.             break;  
  60.         }  
  61.   
  62.         return super.onTouchEvent(event);  
  63.     }  
  64.   
  65.     @Override  
  66.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  67.     {  
  68.           
  69.         int action = ev.getAction();  
  70.         switch (action)  
  71.         {  
  72.         case MotionEvent.ACTION_DOWN:  
  73.             Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");  
  74.             break;  
  75.         case MotionEvent.ACTION_MOVE:  
  76.             Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");  
  77.             break;  
  78.         case MotionEvent.ACTION_UP:  
  79.             Log.e(TAG, "onInterceptTouchEvent ACTION_UP");  
  80.             break;  
  81.   
  82.         default:  
  83.             break;  
  84.         }  
  85.           
  86.         return super.onInterceptTouchEvent(ev);  
  87.     }  
  88.   
  89.     @Override  
  90.     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)  
  91.     {  
  92.         Log.e(TAG, "requestDisallowInterceptTouchEvent ");  
  93.         super.requestDisallowInterceptTouchEvent(disallowIntercept);  
  94.     }  
  95.   
  96. }  

继承LinearLayout,然后复写了与事件分发机制有关的代码,添加上了日志的打印~

然后看我们的布局文件:

[html] view plain copy
  1. <com.example.zhy_event03.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.     <com.example.zhy_event03.MyButton  
  8.         android:id="@+id/id_btn"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="click me" />  
  12.   
  13. </com.example.zhy_event03.MyLinearLayout>  

MyLinearLayout中包含一个MyButton,MyButton都上篇博客中已经出现过,这里就不再贴代码了,不清楚可以去查看~

然后MainActivity就是直接加载布局,没有任何代码~~~

直接运行我们的代码,然后点击我们的Button,依然是有意的MOVE一下,不然不会触发MOVE事件,看一下日志的输出:

[html] view plain copy
  1. 09-06 09:57:27.287: E/MyLinearLayout(959): dispatchTouchEvent ACTION_DOWN  
  2. 09-06 09:57:27.287: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_DOWN  
  3. 09-06 09:57:27.287: E/MyButton(959): dispatchTouchEvent ACTION_DOWN  
  4. 09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_DOWN  
  5. 09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_MOVE  
  6. 09-06 09:57:27.327: E/MyLinearLayout(959): dispatchTouchEvent ACTION_MOVE  
  7. 09-06 09:57:27.327: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_MOVE  
  8. 09-06 09:57:27.337: E/MyButton(959): dispatchTouchEvent ACTION_MOVE  
  9. 09-06 09:57:27.337: E/MyButton(959): onTouchEvent ACTION_MOVE  
  10. 09-06 09:57:27.457: E/MyLinearLayout(959): dispatchTouchEvent ACTION_UP  
  11. 09-06 09:57:27.457: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_UP  
  12. 09-06 09:57:27.457: E/MyButton(959): dispatchTouchEvent ACTION_UP  
  13. 09-06 09:57:27.457: E/MyButton(959): onTouchEvent ACTION_UP  

可以看到大体的事件流程为:

MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent 

可以看出,在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身~

下面我们按照日志的输出,进入源码~

2、源码分析

ViewGroup - dispatchTouchEvent

1、ViewGroup - dispatchTouchEvent - ACTION_DOWN

首先是ViewGroup的dispatchTouchEvent方法:

[java] view plain copy
  1. @Override  
  2.    public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.        if (!onFilterTouchEventForSecurity(ev)) {  
  4.            return false;  
  5.        }  
  6.   
  7.        final int action = ev.getAction();  
  8.        final float xf = ev.getX();  
  9.        final float yf = ev.getY();  
  10.        final float scrolledXFloat = xf + mScrollX;  
  11.        final float scrolledYFloat = yf + mScrollY;  
  12.        final Rect frame = mTempRect;  
  13.   
  14.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  15.   
  16.        if (action == MotionEvent.ACTION_DOWN) {  
  17.            if (mMotionTarget != null) {  
  18.                // this is weird, we got a pen down, but we thought it was  
  19.                // already down!  
  20.                // XXX: We should probably send an ACTION_UP to the current  
  21.                // target.  
  22.                mMotionTarget = null;  
  23.            }  
  24.            // If we're disallowing intercept or if we're allowing and we didn't  
  25.            // intercept  
  26.            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  27.                // reset this event's action (just to protect ourselves)  
  28.                ev.setAction(MotionEvent.ACTION_DOWN);  
  29.                // We know we want to dispatch the event down, find a child  
  30.                // who can handle it, start with the front-most child.  
  31.                final int scrolledXInt = (int) scrolledXFloat;  
  32.                final int scrolledYInt = (int) scrolledYFloat;  
  33.                final View[] children = mChildren;  
  34.                final int count = mChildrenCount;  
  35.   
  36.                for (int i = count - 1; i >= 0; i--) {  
  37.                    final View child = children[i];  
  38.                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  39.                            || child.getAnimation() != null) {  
  40.                        child.getHitRect(frame);  
  41.                        if (frame.contains(scrolledXInt, scrolledYInt)) {  
  42.                            // offset the event to the view's coordinate system  
  43.                            final float xc = scrolledXFloat - child.mLeft;  
  44.                            final float yc = scrolledYFloat - child.mTop;  
  45.                            ev.setLocation(xc, yc);  
  46.                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  47.                            if (child.dispatchTouchEvent(ev))  {  
  48.                                // Event handled, we have a target now.  
  49.                                mMotionTarget = child;  
  50.                                return true;  
  51.                            }  
  52.                            // The event didn't get handled, try the next view.  
  53.                            // Don't reset the event's location, it's not  
  54.                            // necessary here.  
  55.                        }  
  56.                    }  
  57.                }  
  58.            }  
  59.        }                                                                                                                                                  ....//other code omitted  
代码比较长,决定分段贴出,首先贴出的是ACTION_DOWN事件相关的代码。

16行:进入ACTION_DOWN的处理

17-23行:将mMotionTarget置为null

26行:进行判断:if(disallowIntercept || !onInterceptTouchEvent(ev))

两种可能会进入IF代码段

1、当前不允许拦截,即disallowIntercept =true,

2、当前允许拦截但是不拦截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;

注:disallowIntercept 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean);进行设置,后面会详细说;而onInterceptTouchEvent(ev)可以进行复写。

36-57行:开始遍历所有的子View

41行:进行判断当前的x,y坐标是否落在子View身上,如果在,47行,执行child.dispatchTouchEvent(ev),就进入了View的dispatchTouchEvent代码中了,如果不了解请参考:Android View的事件分发机制,当child.dispatchTouchEvent(ev)返回true,则为mMotionTarget=child;然后return true;

ViewGroup的ACTION_DOWN分析结束,总结一下:

ViewGroup实现捕获到DOWN事件,如果代码中不做TOUCH事件拦截,则开始查找当前x,y是否在某个子View的区域内,如果在,则把事件分发下去。


按照日志,接下来到达ACTION_MOVE

2、ViewGroup - dispatchTouchEvent - ACTION_MOVE

首先我们源码进行删减,只留下MOVE相关的代码:

[java] view plain copy
  1. @Override  
  2.    public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.        final int action = ev.getAction();  
  4.        final float xf = ev.getX();  
  5.        final float yf = ev.getY();  
  6.        final float scrolledXFloat = xf + mScrollX;  
  7.        final float scrolledYFloat = yf + mScrollY;  
  8.        final Rect frame = mTempRect;  
  9.   
  10.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  11.   
  12.       //...ACTION_DOWN  
  13.   
  14.       //...ACTIN_UP or ACTION_CANCEL  
  15.   
  16.        // The event wasn't an ACTION_DOWN, dispatch it to our target if  
  17.        // we have one.  
  18. final View target = mMotionTarget;  
  19.        
  20.   
  21.        // if have a target, see if we're allowed to and want to intercept its  
  22.        // events  
  23.        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  24.            //....  
  25.        }  
  26.        // finally offset the event to the target's coordinate system and  
  27.        // dispatch the event.  
  28.        final float xc = scrolledXFloat - (float) target.mLeft;  
  29.        final float yc = scrolledYFloat - (float) target.mTop;  
  30.        ev.setLocation(xc, yc);  
  31.   
  32.        return target.dispatchTouchEvent(ev);  
  33.    }  

18行:把ACTION_DOWN时赋值的mMotionTarget,付给target ; 

23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 当前允许拦截且拦截了,才进入IF体,当然了默认是不会拦截的~这里执行了onInterceptTouchEvent(ev)

28-30行:把坐标系统转化为子View的坐标系统

32行:直接return target.dispatchTouchEvent(ev); 

可以看到,正常流程下,ACTION_MOVE在检测完是否拦截以后,直接调用了子View.dispatchTouchEvent,事件分发下去;

最后就是ACTION_UP了

3、ViewGroup - dispatchTouchEvent - ACTION_UP

[java] view plain copy
  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.        if (!onFilterTouchEventForSecurity(ev)) {  
  3.            return false;  
  4.        }  
  5.   
  6.        final int action = ev.getAction();  
  7.        final float xf = ev.getX();  
  8.        final float yf = ev.getY();  
  9.        final float scrolledXFloat = xf + mScrollX;  
  10.        final float scrolledYFloat = yf + mScrollY;  
  11.        final Rect frame = mTempRect;  
  12.   
  13.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  14.   
  15.        if (action == MotionEvent.ACTION_DOWN) {...}  
  16.   
  17. boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  18.                (action == MotionEvent.ACTION_CANCEL);  
  19.   
  20. if (isUpOrCancel) {  
  21.            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  22.        }  
  23. final View target = mMotionTarget;  
  24. if(target ==null ){...}  
  25. if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}  
  26.   
  27.        if (isUpOrCancel) {  
  28.            mMotionTarget = null;  
  29.        }  
  30.   
  31.        // finally offset the event to the target's coordinate system and  
  32.        // dispatch the event.  
  33.        final float xc = scrolledXFloat - (float) target.mLeft;  
  34.        final float yc = scrolledYFloat - (float) target.mTop;  
  35.        ev.setLocation(xc, yc);  
  36.   
  37.        return target.dispatchTouchEvent(ev);  
  38.    }  

17行:判断当前是否是ACTION_UP

21,28行:分别重置拦截标志位以及将DOWN赋值的mMotionTarget置为null,都UP了,当然置为null,下一次DOWN还会再赋值的~

最后,修改坐标系统,然后调用target.dispatchTouchEvent(ev);


正常情况下,即我们上例整个代码的流程我们已经走完了:

1、ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用mMotionTarget.dispatchTouchEvent

2、ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

3、ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

当然了在分发之前都会修改下坐标系统,把当前的x,y分别减去child.left 和 child.top ,然后传给child;


3、关于拦截

1、如何拦截

上面的总结都是基于:如果没有拦截;那么如何拦截呢?

复写ViewGroup的onInterceptTouchEvent方法:

[java] view plain copy
  1. @Override  
  2.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  3.     {  
  4.         int action = ev.getAction();  
  5.         switch (action)  
  6.         {  
  7.         case MotionEvent.ACTION_DOWN:  
  8.             //如果你觉得需要拦截  
  9.             return true ;   
  10.         case MotionEvent.ACTION_MOVE:  
  11.             //如果你觉得需要拦截  
  12.             return true ;   
  13.         case MotionEvent.ACTION_UP:  
  14.             //如果你觉得需要拦截  
  15.             return true ;   
  16.         }  
  17.           
  18.         return false;  
  19.     }  

默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。

原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ; 

2、如何不被拦截

如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;

此时子View希望依然能够响应MOVE和UP时该咋办呢?

Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截,我们在子View的dispatchTouchEvent中直接这么写:

[java] view plain copy
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent event)  
  3.     {  
  4.         getParent().requestDisallowInterceptTouchEvent(true);    
  5.         int action = event.getAction();  
  6.   
  7.         switch (action)  
  8.         {  
  9.         case MotionEvent.ACTION_DOWN:  
  10.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
  11.             break;  
  12.         case MotionEvent.ACTION_MOVE:  
  13.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
  14.             break;  
  15.         case MotionEvent.ACTION_UP:  
  16.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
  17.             break;  
  18.   
  19.         default:  
  20.             break;  
  21.         }  
  22.         return super.dispatchTouchEvent(event);  
  23.     }  

getParent().requestDisallowInterceptTouchEvent(true);  这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。


从源码也可以解释:

ViewGroup MOVE和UP拦截的源码是这样的:

[java] view plain copy
  1. if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  2.             final float xc = scrolledXFloat - (float) target.mLeft;  
  3.             final float yc = scrolledYFloat - (float) target.mTop;  
  4.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  5.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  6.             ev.setLocation(xc, yc);  
  7.             if (!target.dispatchTouchEvent(ev)) {  
  8.                 // target didn't handle ACTION_CANCEL. not much we can do  
  9.                 // but they should have.  
  10.             }  
  11.             // clear the target  
  12.             mMotionTarget = null;  
  13.             // Don't dispatch this event to our own view, because we already  
  14.             // saw it when intercepting; we just want to give the following  
  15.             // event to the normal onTouchEvent().  
  16.             return true;  
  17.         }  

当我们把disallowIntercept设置为true时,!disallowIntercept直接为false,于是拦截的方法体就被跳过了~

注:如果ViewGroup在onInterceptTouchEvent(ev)  ACTION_DOWN里面直接return true了,那么子View是木有办法的捕获事件的~~~


4、如果没有找到合适的子View

我们的实例,直接点击ViewGroup内的按钮,当然直接很顺利的走完整个流程;

但是有两种特殊情况

1、ACTION_DOWN的时候,子View.dispatchTouchEvent(ev)返回的为false ; 

如果你仔细看了,你会注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代码是这样的

[java] view plain copy
  1. if (child.dispatchTouchEvent(ev))  {  
  2.                               // Event handled, we have a target now.  
  3.                               mMotionTarget = child;  
  4.                               return true;  
  5.                           }  

只有在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,即mMotionTarget = child;

但是如果返回false,那么mMotionTarget 依然是null

mMotionTarget 为null会咋样呢?

其实ViewGroup也是View的子类,如果没有找到能够处理该事件的子View,或者干脆就没有子View;

那么,它作为一个View,就相当于View的事件转发了~~直接super.dispatchTouchEvent(ev);

源码是这样的:

[java] view plain copy
  1. final View target = mMotionTarget;  
  2.        if (target == null) {  
  3.            // We don't have a target, this means we're handling the  
  4.            // event as a regular view.  
  5.            ev.setLocation(xf, yf);  
  6.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  7.                ev.setAction(MotionEvent.ACTION_CANCEL);  
  8.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  9.            }  
  10.            return super.dispatchTouchEvent(ev);  
  11.        }  

我们没有一个能够处理该事件的目标元素,意味着我们需要自己处理~~~就相当于传统的View~

2、那么什么时候子View.dispatchTouchEvent(ev)返回的为true


如果你仔细看了上篇博客,你会发现只要子View支持点击或者长按事件一定返回true~~


源码是这样的:

[java] view plain copy
  1.    
  2. if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  3.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {     
  4.              return true ;                                                                                                                                                                                                                                                                                                   }  

5、总结


关于代码流程上面已经总结过了~


1、如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;


2、可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法


3、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup对其MOVE或者UP事件进行拦截;


好了,那么实际应用中能解决哪些问题呢?


比如你需要写一个类似slidingmenu的左侧隐藏menu,主Activity上有个Button、ListView或者任何可以响应点击的View,你在当前View上死命的滑动,菜单栏也出不来;因为MOVE事件被子View处理了~ 你需要这么做:在ViewGroup的dispatchTouchEvent中判断用户是不是想显示菜单,如果是,则在onInterceptTouchEvent(ev)拦截子View的事件;自己进行处理,这样自己的onTouchEvent就可以顺利展现出菜单栏了~~

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 轻轨少买了一站怎么办 高铁火车票丢了怎么办 如果高铁票丢了怎么办 高铁票丢了怎么办 报销 高铁如果没赶上怎么办 高铁管家待核验怎么办 动车没有票了怎么办 12306取消订单3次怎么办 【12306取消订单3次怎么办】 火车票取消订单3次怎么办 12306收不到验证码怎么办 安逸花验证码次数限制怎么办 航班晚点导致错过转机怎么办 想去沈阳站送站怎么办 高铁没有赶上车怎么办 火车晚点赶不上下一趟车怎么办 列车晚点影响下一趟车怎么办? 高铁晚点赶不上下班车怎么办 火车在半路坏了怎么办 做火车中途坏了怎么办 员工怀孕不上班保险怎么办 怀孕带孩子不能上班保险怎么办 怀孕了不想上班保险怎么办 高铁票没票了怎么办 购买动车票无座怎么办 个税工资多报怎么办 火车晚点耽误了转车怎么办 坐火车联系不上怎么办 号码被别人注册了12306怎么办 注册12306的号码换了怎么办 12306号码被注册了怎么办 12306身份证被注册了怎么办 12306被别人注册了怎么办 铁路1236注册名忘记了怎么办 12306手机被别人注册了怎么办 12306注册手机不用了怎么办 到站后火车票掉了怎么办 在手机上买了票怎么办 智行火车票抢不到票怎么办 高铁买票票丢了怎么办 异地恋房费太贵怎么办