Android 可拖動懸浮窗實現(xiàn)

最近公司的項目里,需要通過懸浮窗進行控制,懸浮窗根據(jù)手勢進行拖動食零。當時同事給的建議用 ViewDragHelper 來實現(xiàn)(原諒沒玩過這個東西,網(wǎng)上看了下教程挺牛逼的寂屏,算了贰谣,還是選擇用手勢監(jiān)聽做吧),首先先給大伙看下最終的項目實現(xiàn)效果(模擬器上可能會卡頓迁霎,實際的運行效果還是很流暢的)吱抚。當然,最后我也不會把公司項目代碼分享給大家伙考廉,這里就給大家講解下實現(xiàn)的思路秘豹。


項目最終效果

看完效果圖,希望你能有點感興趣昌粤,然后就開始上代碼啦~既绕,首先通過 WindowManager 添加一個指示的 indicatorView(就是側(cè)邊紅色的條),用來提示用戶通過這邊進行拖動懸浮窗涮坐,接著在手指在 indicatorView 按下的時候凄贩,添加一個空的 RelativeLayout,作為懸浮窗的 rootview袱讹,然后往 rootview 添加懸浮窗內(nèi)容 contentView疲扎,通過 layout 方法,改變 contentView 的布局參數(shù)捷雕,也可以通過 LayoutParam 來設(shè)置评肆,實現(xiàn)最終的效果》乔可能文字表達不夠明確瓜挽,貼一張手繪原理圖
原理圖

接下來就是代碼一波流了,首先定義一個手勢監(jiān)聽回調(diào)類征绸,主要用來判斷 indicatorView 的滑動的距離以及方向久橙,然后懸浮窗可以根據(jù) indicatorView 的滑動方向進行拖動

public abstract class OnFlingListener {
    // 手指按下
    public void onFingerDown() {
    }

    // 手指抬起
    public void onFingerUp(int slideDirection) {
    }

    // 手勢上滑
    public void onScrollUp(float scrollY) {
    }

    // 手勢下滑
    public void onScrollDown(float scrollY) {
    }

    // 手勢左滑
    public void onScrollLeft(float scrollX) {
    }

    // 手勢右滑
    public void onScrollRight(float scrollX) {
    }
}

定義完手勢回調(diào),就需要定義用來監(jiān)聽拖動手勢的 indicatorView 啦管怠,其主要作用是當焦點落到 indicatorView 的時候淆衷,通過用戶的手勢來拖動懸浮窗活動,這個可以根據(jù)自己的喜好進行編寫

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/floating_bar_outside">

    <TextView
        android:id="@+id/touch_view"
        android:layout_width="200dp"
        android:layout_height="5dp"
        android:background="@color/colorAccent"
        android:clickable="true"
        android:focusable="true" />
</LinearLayout>

方便起見渤弛,我這邊用 TextView 來作為 indicatorView祝拯,做好準備工作就要開始編寫實際的操作邏輯啦,首先定義一個內(nèi)部手勢類,用來實現(xiàn)操作邏輯

class FloatViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
        // 回調(diào)類
        private OnFlingListener mFlingListener;
        
        // ..... 省略部分無關(guān)代碼

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            float x = e2.getX() - e1.getX();
            float y = e2.getY() - e1.getY();
            float x_abs = Math.abs(x);
            float y_abs = Math.abs(y);

            scrollX = e2.getX();
            scrollY = e2.getY();

            // x > y 且 x 滑動距離大于閥值佳头,則是水平滑動鹰贵,否則是垂直滑動
            if (x_abs >= y_abs && x_abs > X_SLOP) {
                // 如果 x 的滑動距離大于 0 則是右滑,否則為左滑
                if (x > 0 && mFlingListener != null) {
                    mOnFlingListener.onScrollRight(x_abs);
                } else if (x < 0 && mFlingListener != null) {
                    mOnFlingListener.onScrollLeft(x_abs);
                }
                // 用來記錄抬手前的最后一下是左滑還是右滑康嘉,最后通過回調(diào)函數(shù)傳回
                mDirection = (scrollX - lastScrollX) >= 0 ? DIRECTION_RIGHT : DIRECTION_LEFT;
            } else if (y_abs >= x_abs && y_abs > Y_SLOP) {
                // 如果 y 的滑動距離大于 0 則是下滑碉输, 否則上滑
                if (y > 0 && mFlingListener != null) {
                    mOnFlingListener.onScrollDown(y_abs);
                } else if (y < 0 && mFlingListener != null) {
                    mOnFlingListener.onScrollUp(y_abs);
                }
                // 用來記錄抬手前最后一下是上滑還是下滑
                mDirection = (scrollY - lastScrollY >= 0) ? DIRECTION_DOWN : DIRECTION_UP;
            }

            lastScrollX = scrollX;
            lastScrollY = scrollY;

            return super.onScroll(e1, e2, distanceX, distanceY);
        }
    }

這里通過兩次手指的位置,來判斷當前手指的滑動方向亭珍。通過比較 x 軸的移動距離和 y 的移動距離敷钾,判斷是上下滑動還是左右滑動,然后通過滑動的距離是否大于 0 判斷滑動的方向肄梨,因為當你的 indicatorView 在右側(cè)的時候阻荒,如果初始滑動距離大于 0 的話,根本就是不可能的众羡。最后還需要判斷最后一下手指的滑動方向侨赡,如果和初始的方向相反邑时,則需要將拖出來的懸浮窗自動回滾到初始狀態(tài)驼仪。

接著就需要實現(xiàn)對的 indicatorView 做手勢監(jiān)聽

mTouchView.setOnTouchListener(new OnTouchListener() {
    @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 按下時候震動提醒
                    mVibrator.vibrate(50);
                    mGestureDetector.onTouchEvent(event);
                    if (mOnFlingListener != null)
                        mOnFlingListener.onFingerDown();
                    break;

                case MotionEvent.ACTION_MOVE:
                    mGestureDetector.onTouchEvent(event);
                    break;

                case MotionEvent.ACTION_UP:
                    if (mOnFlingListener != null)
                        // 將最后的滑動方向通過回調(diào)傳出
                        mOnFlingListener.onFingerUp(mDirection);
                        // 抬手的時候注意把 direction 回歸初始狀態(tài)
                    mDirection = NO_ACTION;
            }
            return true;
        }
    });

當手指按下的時候梳星,做下震動,用于提示作用甜害,然后根據(jù)不同的手勢操作,做相應(yīng)的回調(diào)球昨,當抬手指的時候尔店,記得需要將手勢方向設(shè)置回初始值,OK主慰,indicatorView 的內(nèi)容大概就那么多嚣州,具體的操作,需要通過懸浮窗 FloatWindow 去實現(xiàn)共螺。在實現(xiàn)邏輯之前该肴,因為整體都在懸浮窗上實現(xiàn),需要定義懸浮窗內(nèi)容的一些必要屬性藐不,因為 indicatorView 和 rootView 的屬性差不多匀哄,所以只列出 indicatorView 的屬性列表,具體的可以看 demo

mParams = new WindowManager.LayoutParams();
mParams.packageName = FloatingApplication.getContext().getPackageName();
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 當懸浮窗顯示的時候可以獲取到焦點
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

// 需要適配 8.0雏蛮,當 8.0 以上的版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    mParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
mParams.format = PixelFormat.RGBA_8888;

flag 作用主要是讓懸浮窗能夠獲取到焦點涎嚼,能夠消費點擊等事件,還需要注意的是挑秉,在 8.0 之后的版本法梯,懸浮窗 type 只能使用 TYPE_APPLICATION_OVERLAY 之前的 type 都過時無效了。定義完屬性就需要定義 indicatorView 犀概, rootview 以及懸浮窗具體的內(nèi)容 contentView

 public void initFloatView() {
        mFloatView = new FloatView(BaseApplication.getContext(), new OnFlingListener() {
            @Override
            public void onFingerDown() {
                // 添加 rootview立哑,如果已經(jīng)存在了夜惭,直接根據(jù) params 進行更新布局就行,如果不存在就添加
                try {
                    mWindowManager.updateViewLayout(mContainer, mContainerParams);
                } catch (Exception e) {
                    e.printStackTrace();
                    mWindowManager.addView(mContainer, mContainerParams);
                    // 判斷懸浮窗是否已經(jīng)顯示的標志位
                    isCenterShow = true;
                }
                // 根據(jù)布局獲取懸浮窗 contentView
                mContentView = LayoutInflater.from(mContext).inflate(R.layout.content_view, null);
                // 懸浮窗 contentView 布局屬性
                RelativeLayout.LayoutParams contentLp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
                mContentView.setLayoutParams(contentLp);
                // 剛開始添加的時候設(shè)置不可見刁憋,因為設(shè)置了 LayoutParam 是 MATCH_PARENT滥嘴,等拖動操作再顯示即可
                mContentView.setVisibility(View.INVISIBLE);
                mContainer.addView(mContentView);
                // 根據(jù)滑動的方向,設(shè)置最開始的布局位置
                switch (mSlideType) {
                    // 從右往左滑動至耻,懸浮窗內(nèi)容全部位于屏幕的右側(cè)若皱,所以此時的 left, right 屬性都是屏幕的寬度
                    case SLIDE_RIGHT_TO_LEFT:
                        mContentView.layout(mScreenWidth, 0, mScreenWidth, mScreenHeight);
                        break;
                    // ...省略其他方向的,原理類似尘颓,具體看 demo...
                }
                
                // 設(shè)置 contentView 空白處的點擊事件走触,點擊消失,根據(jù)具體的滑動方向做不同的動畫效果
                mContentView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        switch (mSlideType) {
                            case SLIDE_RIGHT_TO_LEFT:
                                rightInSmoothToRight();
                                break;
                            // ...省略其他方向的疤苹,原理類似互广,具體看 demo...
                        }
                    }
                });
            }

            @Override
            public void onFingerUp(int direction) {
                // 當手指抬起來的時候,根據(jù)最后一下手勢卧土,即 direction 返回的值惫皱,判斷滑動的方向,選擇 contentView 是否顯示出來尤莺,還是回退不顯示旅敷,然后做不同的動畫
                switch (mSlideType) {
                     // 右 -> 左
                    case SLIDE_RIGHT_TO_LEFT:
                        if (direction == FloatView.DIRECTION_LEFT) {
                            // 右側(cè)進入,滑到左側(cè)展開懸浮窗內(nèi)容的動畫
                            rightInSmoothToLeft();
                        } else {
                            // 右側(cè)進入颤霎,滑到右側(cè)回到初始狀態(tài)的動畫
                            rightInSmoothToRight();
                        }
                        break;
                    // ...省略其他方向的媳谁,原理類似,具體看 demo...
                }
            }

            @Override
            public void onScrollLeft(float scrollX) {
                // 從右側(cè)滑到左側(cè)友酱,根據(jù)手勢滑動的距離晴音,不斷改變 left 的屬性值
                if (mSlideType == SLIDE_RIGHT_TO_LEFT) {
                    mContentView.layout(mScreenWidth - (int) scrollX, 0, mScreenWidth, mScreenHeight);
                    mContentView.setVisibility(View.VISIBLE);
                }
            }
        });
    }

這里代碼會比較多,適當?shù)姆治鱿碌奚迹紫却冈辏斒种赴聪碌臅r候,需要先把 rootview 放到 windowManager 中或详,因為是透明的进苍,所以看上去就是桌面的內(nèi)容,然后需要去取得 contentView 并放到 rootview 上鸭叙,因為一開始不能顯示 contentView觉啊,所以設(shè)置 contentView 的不可見,然后沈贝,根據(jù) indicatorView 的位置杠人,設(shè)置 contentView 的位置屬性,例如 indicatorView 在屏幕的右側(cè),那么 contentView 也必須在屏幕的右側(cè)嗡善,當拖動 indicatorView 的時候再慢慢的顯示 contentView 就實現(xiàn)了拖動效果辑莫,所以 contentView 一開始 layout 的位置就是 (mScreenWidth, 0, mScreenWidth, mScreenHeight),其他的方向同理罩引。然后根據(jù)手勢的滑動方向和距離各吨,通過動畫不斷去改變 contentView 的 layout 屬性,并將 contentView 從不可見設(shè)置為可見袁铐,給用戶的感覺就有將懸浮窗一點點拖出來的效果了揭蜒。等到懸浮窗完全展示的時候,點擊空白的地方剔桨,懸浮窗又需要從當前的位置回滾到初始的位置屉更,其原理和拖出來的原理是一樣的。通過如上代碼可以發(fā)現(xiàn)洒缀,contentView 的 layout 屬性變化都是通過動畫來實現(xiàn)的瑰谜,這邊我采用屬性動畫,來不斷改變滑動的距離來實現(xiàn)懸浮窗顯示和隱藏的效果树绩,也就是就是上面代碼中的 rightInSmoothToLeftrightInSmoothToRight動畫的實現(xiàn)萨脑,不多解釋,直接上代碼和注釋

/**
 * 右側(cè)滑進饺饭,滑到頁面左側(cè)渤早,進入動畫
 */
private void rightInSmoothToLeft() {
    int posX = mScreenWidth - mContentView.getWidth();
    // 通過屬性動畫做最后的效果,右側(cè)滑進到左側(cè)砰奕,contentView 的頁面從右側(cè)開始向左側(cè)滑動顯示蛛芥,那么 right 始終保持是屏幕的寬度不變提鸟,改變的是 left 屬性军援,
    //從屏幕寬的值一直改變到 0,那屬性動畫的間隔就出來了称勋,時間設(shè)置整體的滑動為 300 ms胸哥,那么剩下的距離需要的滑動時間就是 300 * posX / mScreenWidth
    ValueAnimator slideLeftAnim = ValueAnimator.ofInt(posX, 0).setDuration(300 * posX / mScreenWidth);
    slideLeftAnim.setInterpolator(new AccelerateDecelerateInterpolator());
    slideLeftAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 根據(jù)變化的值,重新設(shè)置 contentView 的布局
            int pos = (int) animation.getAnimatedValue();
            mContentView.layout(pos, 0, mScreenWidth, mScreenHeight);
        }
    });

    slideLeftAnim.start();
}

 /**
  * 右側(cè)滑進赡鲜,滑到頁面右側(cè)空厌,退出動畫
  */
  private void rightInSmoothToRight() {
      int posX = mScreenWidth - mContentView.getWidth();
      // 同理,退出的動畫就是 contentView 從當前的 left 的值一直變換到屏幕寬的值银酬,也可以得到相應(yīng)動畫
      ValueAnimator slideRightAnim = ValueAnimator.ofInt(posX, mScreenWidth).setDuration(300 * (mScreenWidth - posX) / mScreenWidth);
      slideRightAnim.setInterpolator(new AccelerateDecelerateInterpolator());
      slideRightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator animation) {
             // 根據(jù)變化的值嘲更,重新設(shè)置 contentView 的布局
              int pos = (int) animation.getAnimatedValue();
              mContentView.layout(pos, 0, mScreenWidth, mScreenHeight);
          }
      });

      slideRightAnim.addListener(new AnimatorListenerAdapter() {
          @Override
          public void onAnimationEnd(Animator animation) {
              super.onAnimationEnd(animation);
              // 退出動畫結(jié)束的時候,需要把 rootview 從 WindowManager 移除
              dismissContentView();
          }
      });
      slideRightAnim.start();
}

到現(xiàn)在為止揩瞪,我們已經(jīng)搞定所有的邏輯赋朦,就差將 indicatorView 顯示到視圖上就大功告成了,通過一個 show 方法將顯示的邏輯放到外部的 Activity 或者 Service 調(diào)用

/**
 * 顯示 indicatorView
 */
public void show() {
    try {
        mWindowManager.updateViewLayout(mFloatView, mParams);
    } catch (IllegalArgumentException e) {
        mWindowManager.addView(mFloatView, mParams);
    }
}

在調(diào)用 show 方法之前,如果版本大于 23 需要檢測懸浮窗權(quán)限才行宠哄,檢測的方法很簡單

public static boolean hasOverlayPermission(Context context) {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
    }

如果沒有同意懸浮窗權(quán)限壹将,則需要引導用戶打開,我這邊通過一個 dialog 實現(xiàn)引導

private void overlayPermissionRequest() {
        mOverlayAskDialog = new AlertDialog.Builder(MainActivity.this)
                .setTitle("Overlay Permission Request")
                .setMessage("Need Overlay Permission")
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
                            startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST);
                        }
                        mOverlayAskDialog.dismiss();
                    }
                })
                .setCancelable(false)
                .create();
        mOverlayAskDialog.show();
    }

當跳轉(zhuǎn)權(quán)限頁面的時候最好用 startActivityForResult 方法毛嫉,當用戶從權(quán)限設(shè)置頁面回來的時候诽俯,通過 onActivityResult 方法再去檢測一次是否真正同意了權(quán)限,如果還是未同意承粤,那就再次引導用戶去同意權(quán)限暴区。這里附上 demo 的效果,雖然和實際項目的效果還是有差別密任,但是核心思想在這了

手勢滑動懸浮框

最后雙手捧上源碼 懸浮窗源碼颜启,望大爺笑納~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浪讳,隨后出現(xiàn)的幾起案子缰盏,更是在濱河造成了極大的恐慌,老刑警劉巖淹遵,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件口猜,死亡現(xiàn)場離奇詭異,居然都是意外死亡透揣,警方通過查閱死者的電腦和手機济炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辐真,“玉大人须尚,你說我怎么就攤上這事∈淘郏” “怎么了耐床?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長楔脯。 經(jīng)常有香客問我撩轰,道長,這世上最難降的妖魔是什么昧廷? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任堪嫂,我火速辦了婚禮,結(jié)果婚禮上木柬,老公的妹妹穿的比我還像新娘皆串。我一直安慰自己,他們只是感情好眉枕,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布恶复。 她就那樣靜靜地躺著娇唯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寂玲。 梳的紋絲不亂的頭發(fā)上塔插,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音拓哟,去河邊找鬼想许。 笑死,一個胖子當著我的面吹牛断序,可吹牛的內(nèi)容都是我干的流纹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼违诗,長吁一口氣:“原來是場噩夢啊……” “哼漱凝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诸迟,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤茸炒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阵苇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壁公,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年绅项,在試婚紗的時候發(fā)現(xiàn)自己被綠了紊册。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡快耿,死狀恐怖囊陡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掀亥,我是刑警寧澤撞反,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站铺浇,受9級特大地震影響痢畜,放射性物質(zhì)發(fā)生泄漏垛膝。R本人自食惡果不足惜鳍侣,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吼拥。 院中可真熱鬧倚聚,春花似錦、人聲如沸凿可。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惨驶,卻和暖如春白热,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粗卜。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工屋确, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人续扔。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓攻臀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纱昧。 傳聞我的和親對象是個殘疾皇子刨啸,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評論 3 119
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,841評論 25 707
  • 皚如山上雪识脆,皎若云間月设联。 聞君有兩意,故來相決絕灼捂。 今日斗酒會仑荐,明旦溝水頭。 躞蹀御溝上纵东,溝水東西流粘招。 凄凄復凄凄...
    terrence_zhan閱讀 154評論 0 0
  • 來肯在線由2014年提供服務(wù)企業(yè)電算化到企業(yè)全面的管理軟件--在線進銷存與行業(yè)解決方案,服務(wù)企業(yè)信息化偎球。在云計算興...
    payaso閱讀 287評論 0 1