成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

需求解決系列一之移動卡片實現(xiàn)答題功能

Mr_zhang / 846人閱讀

摘要:二這個單詞隨手勢的移動單詞塊相比較上面流式布局的實現(xiàn),這個就相對復(fù)雜多了。在這個模塊中,我們需要實現(xiàn)以下邏輯。

前言

前兩天在改完APP的一些bug之后逛了一下貼吧,在Android開發(fā)吧中很驚喜的發(fā)現(xiàn)了一個朋友在尋求幫助。為什么說驚喜呢?因為現(xiàn)在這個貼吧已經(jīng)淪為了接畢設(shè)課設(shè)的重災(zāi)區(qū),少有人在這里討論技術(shù)了。話說回來,這位朋友的問題是這樣的。

看到之后我覺得還是挺有意思的,加上工作也不是特別忙,就試著做了一下,下面是做成的效果。

實現(xiàn)思路

每次得到一個新的需求的時候,要將一個大的需求進(jìn)行劃分,劃分成主要的和次要的小需求,在這個大需求里面,“請將卡片移動到正確位置”,“跳過此題”和最后正確答案的顯示都是非常容易實現(xiàn)的小需求,先不管,除此之外有三個重點:
一、流式布局
二、“not”這個單詞隨手勢的移動——單詞塊
三、將單詞插入到原本的句子中
解決了這三點,基本也就實現(xiàn)了這個大需求了。

擼起袖子各個擊破 一、流式布局

這個流式布局主要是為了承載題干的,當(dāng)然使用RecyclerView+LayoutManager來實現(xiàn)是最簡單的,這里我使用的是xiangcman/LayoutManager-FlowLayout,用這個LayoutManager可以很輕松的實現(xiàn)流式布局來承載題干的內(nèi)容。

二、“not”這個單詞隨手勢的移動——單詞塊

相比較上面流式布局的實現(xiàn),這個就相對復(fù)雜多了。主要考察的點是View的事件處理和坐標(biāo)位置換算。我們主要監(jiān)聽“單詞塊”的setOnTouchListener事件,然后處理MotionEvent.ACTION_MOVE事件,讓“單詞塊“隨著我們的手指移動。在這里,我們需要介紹下幾個重要的概念:
event.getRawX() //獲取相對于手機屏幕左上角的距離
event.getX() //獲取以被監(jiān)聽事件控件為坐標(biāo)系的離控件左上角的距離
view.getX() //獲取view相對于其父控件的位置
具體如下圖所示:

要想單詞塊能夠隨著我們的手指移動,我們需要獲取你當(dāng)前手指指尖的位置,然后將單詞塊移動到你手指指尖的位置,然后通過view.setX(x)和view.setY(y)來設(shè)置view的位置,我們通過event.getRawX()和event.getRawY()來獲取我們當(dāng)前手指在整個屏幕中的位置,view.setX(x)中的x是相對于他的父容器的,那么坐標(biāo)的轉(zhuǎn)換就是一個大問題,如下圖所示:

所以最終view.setX(H.x)和view.setY(H.y),這樣就能實現(xiàn)”單詞塊“隨著手指指尖移動了,代碼如下

flow_text.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
                    //記錄手指指尖的位置和代詞塊左上角的x和y的值
                    firstClickX = event.getX();
                    firstClickY = event.getY();
                    //記錄單詞塊父容器和手機屏幕左上角的x和y的值
                    tempX = event.getRawX() - event.getX() - v.getX();
                    tempY = event.getRawY() - event.getY() - v.getY();
                } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) {
                    //移動的時候
                    float positionX = event.getRawX() - firstClickX - tempX;
                    float positionY = event.getRawY() - firstClickY - tempY;
                    v.setX(positionX);
                    v.setY(positionY);
                }
                return false;
            }
        });
三、將單詞插入到原本的句子中

這個是最難實現(xiàn)的,也是最復(fù)雜的。在這個模塊中,我們需要實現(xiàn)以下邏輯?!皢卧~塊”移動到”題干“附近的時候,要開始計算當(dāng)前“單詞塊”的中心點和”題干“中的哪兩個單詞的中間的”縫“最近,然后在這個”縫“所在的位子插入一個沒有內(nèi)容的空格子,以提示用戶你將插入到這個位置,當(dāng)“單詞塊”遠(yuǎn)離題干的時候,不再計算位置;然后在釋放”單詞塊“的時候,如果是在”題干“附近釋放的時候(也就是有提示框出現(xiàn)的時候),將這個單詞插入到剛剛的那個”縫“的位置,然后給出答題的結(jié)果,是放對了還是放錯了,否則就是放棄本次答題,將“單詞塊”放回原來的位置。敘述起來很復(fù)雜,其實跟場景結(jié)合起來,還是很好理解的。

來,我們依然是各個擊破!

檢測“單詞塊”是否移動到”題干“附近
我們可以計算出“單詞塊”的中心點,然后計算出當(dāng)前”題干“(也就是RecyclerView)的位置,如果“單詞塊”的中心點在”題干“的范圍內(nèi),那么就代表進(jìn)入了要監(jiān)聽的范圍了。代碼如下:

flow_text.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
                    //記錄手指指尖的位置和代詞塊左上角的x和y的值
                    firstClickX = event.getX();
                    firstClickY = event.getY();
                    //記錄單詞塊父容器和手機屏幕左上角的x和y的值
                    tempX = event.getRawX() - event.getX() - v.getX();
                    tempY = event.getRawY() - event.getY() - v.getY();
                } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) {
                    //移動的時候
                    float positionX = event.getRawX() - firstClickX - tempX;
                    float positionY = event.getRawY() - firstClickY - tempY;
                    v.setX(positionX);
                    v.setY(positionY);

                    //被移動塊的中點
                    int centerX = ( int ) (positionX + mViewWidth / 2);
                    int centerY = ( int ) (positionY + mViewHeight / 2);
                    //rvY是RecyclerView距離頂部的距離rvHeight是RecyclerView的高度
                    if ( centerY > rvY && centerY < rvHeight + rvY ) {
                       //在范圍內(nèi)了
                    } else {
                       //不在范圍內(nèi)了
                    }
                } 
                return false;
            }
        });

計算當(dāng)前“單詞塊”的中心點和”題干“中的哪兩個單詞的中間的”縫“最近
其實這里有兩種思路,一種通過RecyclerView的適配器獲取到每個item的位置信息,然后計算出兩個item的中間位置,將所有的這些中間位置保存起來,在分別計算“單詞塊”的中心點和這些中間位置的距離,然后再處理,不過用這種方式需要考慮item換行之后中心點計算的問題(由于我沒有使用這種方式,對這個預(yù)期會出現(xiàn)的問題也沒有多加思考);還有一種是在創(chuàng)建題干的時候使用多類型的適配器,在每個單詞中間插入一個占位置的”空格“,這樣就可以直接獲取到這個”空格“的位置作為參照點,同時,這個空格還可以直接給用戶提示位置,一舉兩得。我這里就是用的第二種方式。

找出最近的”縫“

    //找出最近的點 只找沒有內(nèi)容的格子 就是占位格子
    private ItemPositionModel findPoint() {
        //沒有數(shù)據(jù)直接返回
        if ( itemList.isEmpty() )
            return null;
        double distance = Math.sqrt(Math.pow((center.x - itemList.get(0).getCenter().x), 2) +
                Math.pow((center.y - itemList.get(0).getCenter().y), 2));
        int index = 0;
        for ( int i = 1; i < itemList.size(); i++ ) {
            if ( i % 2 == 0 ) {
                double temp = Math.sqrt(Math.pow((center.x - itemList.get(i).getCenter().x), 2) +
                        Math.pow((center.y - itemList.get(i).getCenter().y), 2));
                if ( temp <= distance ) {
                    distance = temp;
                    index = i;
                }
            }
        }
        return itemList.get(index);
    }

找到這個”縫“之后,保存這個”縫“的下標(biāo),刷新適配器,在”縫“這個下標(biāo)處顯示那個用于提示的空格子。

@Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
            ShowItem showItem = list.get(position);
            if ( showItem != null )
                if ( showItem.getType() == 0 ) {
                    //正文內(nèi)容
                    ......
                } else {
                   //currSelectIndex是縫的下標(biāo)
                    if ( currSelectIndex == position ) {
                        (( MyHolderDivider ) holder).tv_divider.setVisibility(View.VISIBLE);
                    } else {
                        (( MyHolderDivider ) holder).tv_divider.setVisibility(View.GONE);
                    }
                }
        }

釋放”單詞塊“的時候

在釋放”單詞塊“的時候,我們需要判斷當(dāng)前是否還在范圍內(nèi),如果是在范圍內(nèi),就在”縫“的地方插入”單詞塊“內(nèi)部的單詞值,然后隱藏掉”單詞塊“,否則,隱藏剛剛用于提示的空格子并將”單詞塊“移動到之前的位置。

//抬起手指的一瞬間
if ( event.getAction() == MotionEvent.ACTION_UP ) {
                    //如果在RecyclerView的范圍內(nèi)才處理 否則回退到原地
                    if ( isInArea ) {
                        //添加成功 移除之前的視圖
                        v.setVisibility(View.GONE);
                        //檢查并設(shè)置結(jié)果 最好提取出來
                        ShowItem result = new ShowItem((( TextView ) v).getText().toString(), 0);
                        if ( rightIndex == currSelectIndex ) {
                            //正確
                            result.setIsRight(1);
                        } else {
                            //錯誤
                            result.setIsRight(2);
                        }
                        list.add(currSelectIndex + 1, result);
                        list.add(currSelectIndex + 2, new ShowItem("", 1)); 
                    } else {
                        //未成功添加抬起的時候回歸原地
                        v.setX(firstX);
                        v.setY(firstY);
                    }
                    //重置位置
                    currSelectIndex = -1;
                    flowAdapter.notifyDataSetChanged();
                }

下面整個是多類型的適配器的代碼,由于比較簡單就寫的比較隨意,沒有多去封裝啥的:

class FlowAdapter extends RecyclerView.Adapter {

        private List list;

        public FlowAdapter(List list) {
            this.list = list;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if ( viewType == 0 ) {
                //正文內(nèi)容類型
                return new MyHolder(View.inflate(MainActivity.this, R.layout.flow_item, null));
            } else {
                //占位符類型
                return new MyHolderDivider(View.inflate(MainActivity.this, R.layout.flow_divider, null));
            }
        }

        @Override
        public int getItemViewType(int position) {
            return list.get(position).getType();
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
            ShowItem showItem = list.get(position);
            if ( showItem != null )
                if ( showItem.getType() == 0 ) {
                    //正文內(nèi)容
                    TextView textView = (( MyHolder ) holder).text;
                    textView.setText(list.get(position).des);
                } else {
                    //是否顯示空格子
                    if ( currSelectIndex == position ) {
                        (( MyHolderDivider ) holder).tv_divider.setVisibility(View.VISIBLE);
                    } else {
                        (( MyHolderDivider ) holder).tv_divider.setVisibility(View.GONE);
                    }
                }
        }

        @Override
        public int getItemCount() {
            return list.size();
        }

        class MyHolder extends RecyclerView.ViewHolder {

            private TextView text;

            public MyHolder(View itemView) {
                super(itemView);
                text = ( TextView ) itemView.findViewById(R.id.flow_text);
            }
        }

        class MyHolderDivider extends RecyclerView.ViewHolder {

            private TextView tv_divider;

            public MyHolderDivider(View itemView) {
                super(itemView);
                tv_divider = ( TextView ) itemView.findViewById(R.id.tv_divider);
            }
        }
    }

最后就是處理用戶答案和正確答案的拼接與顯示工作已經(jīng)對用戶的答案進(jìn)行評判的過程,像什么答案正確顯示綠色,錯誤顯示紅色,比較簡單,就不再贅述,為了減少篇幅,就不再貼出整個代碼了,感興趣的可以查看源碼,我會將源碼放到Github上,如果感覺有用,歡迎star,哈哈。

注:由于時間比較趕,所以有些地方的代碼和命名不是很規(guī)范,敬請諒解。

項目地址和結(jié)語

Github地址: DragDemo

如果連接失效就直接點擊這個鏈接吧!https://github.com/MZCretin/D...

最后感謝 xiangcman/LayoutManager-FlowLayout

我是Cretin,一個可愛的小男孩

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68621.html

相關(guān)文章

  • [聊一聊系列]聊一聊百度移動端首頁前端速度那些事兒

    摘要:要快,但是我們的服務(wù)也必須萬無一失,后續(xù)我會分享百度移動端首頁的前端架構(gòu)設(shè)計那么這樣的優(yōu)化,是如何做到的呢,又如何兼顧穩(wěn)定性,架構(gòu)性,與速度呢別急,讓我們把這些優(yōu)化一一道來。百度移動端首頁的很多就是這樣緩存在客戶端的。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼):https://segmentfault.com/blog/fronte...

    The question 評論0 收藏0
  • 記一次低級并嚴(yán)重的開發(fā)失誤

    摘要:而這一次的項目,原本以為開發(fā)挺順利的,但是開發(fā)完了,才發(fā)現(xiàn)自己犯了一個低級而嚴(yán)重的錯,這樣的一個失誤,我一直耿耿于懷。但是監(jiān)聽用戶退出頁面微信瀏覽器上面的那個返回或者關(guān)閉按鈕卻死活不行。也容易犯一些低級的錯誤。 1.前言 前端從事了超過兩年,修復(fù)了無數(shù)的bug,寫了無數(shù)的bug;挖了很多次坑,填了很多次坑;犯了很多次錯,彌補了很多次,學(xué)習(xí)了很多次。一般而言,對于bug、坑,都是修復(fù)完了...

    wudengzan 評論0 收藏0
  • H5直播答題并不難,看完這篇你也會

    摘要:一的直播答題時什么的直播答題主要發(fā)生在三個環(huán)境下端移動瀏覽器和微信端,微信端包括微信瀏覽器和微信小程序。除了要注意部署邊緣節(jié)點,轉(zhuǎn)碼和中繼也需要部署邊緣幾點,所以微信端直播與答題的同步問題會加重。 各大平臺為了給自家的直播答題爭搶流量,已經(jīng)絞盡腦汁,不斷在玩法上進(jìn)行創(chuàng)新。這場競爭從 iOS 平臺蔓延至 Android 平臺。目前大多數(shù)平臺獲取用戶的方式還是通過分享邀請碼,下載 App ...

    hiyang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<