亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

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

Mr_zhang / 920人閱讀

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

前言

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

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

實(shí)現(xiàn)思路

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

擼起袖子各個(gè)擊破 一、流式布局

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

二、“not”這個(gè)單詞隨手勢(shì)的移動(dòng)——單詞塊

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

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

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

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();
                    //記錄單詞塊父容器和手機(jī)屏幕左上角的x和y的值
                    tempX = event.getRawX() - event.getX() - v.getX();
                    tempY = event.getRawY() - event.getY() - v.getY();
                } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) {
                    //移動(dòng)的時(shí)候
                    float positionX = event.getRawX() - firstClickX - tempX;
                    float positionY = event.getRawY() - firstClickY - tempY;
                    v.setX(positionX);
                    v.setY(positionY);
                }
                return false;
            }
        });
三、將單詞插入到原本的句子中

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

來(lái),我們依然是各個(gè)擊破!

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

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();
                    //記錄單詞塊父容器和手機(jī)屏幕左上角的x和y的值
                    tempX = event.getRawX() - event.getX() - v.getX();
                    tempY = event.getRawY() - event.getY() - v.getY();
                } else if ( event.getAction() == MotionEvent.ACTION_MOVE ) {
                    //移動(dòng)的時(shí)候
                    float positionX = event.getRawX() - firstClickX - tempX;
                    float positionY = event.getRawY() - firstClickY - tempY;
                    v.setX(positionX);
                    v.setY(positionY);

                    //被移動(dòng)塊的中點(diǎn)
                    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;
            }
        });

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

找出最近的”縫“

    //找出最近的點(diǎn) 只找沒(méi)有內(nèi)容的格子 就是占位格子
    private ItemPositionModel findPoint() {
        //沒(méi)有數(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);
    }

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

@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);
                    }
                }
        }

釋放”單詞塊“的時(shí)候

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

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

下面整個(gè)是多類(lèi)型的適配器的代碼,由于比較簡(jiǎn)單就寫(xiě)的比較隨意,沒(méi)有多去封裝啥的:

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)容類(lèi)型
                return new MyHolder(View.inflate(MainActivity.this, R.layout.flow_item, null));
            } else {
                //占位符類(lèi)型
                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)對(duì)用戶的答案進(jìn)行評(píng)判的過(guò)程,像什么答案正確顯示綠色,錯(cuò)誤顯示紅色,比較簡(jiǎn)單,就不再贅述,為了減少篇幅,就不再貼出整個(gè)代碼了,感興趣的可以查看源碼,我會(huì)將源碼放到Github上,如果感覺(jué)有用,歡迎star,哈哈。

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

項(xiàng)目地址和結(jié)語(yǔ)

Github地址: DragDemo

如果連接失效就直接點(diǎn)擊這個(gè)鏈接吧!https://github.com/MZCretin/D...

最后感謝 xiangcman/LayoutManager-FlowLayout

我是Cretin,一個(gè)可愛(ài)的小男孩

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68621.html

相關(guān)文章

  • [聊一聊系列]聊一聊百度移動(dòng)端首頁(yè)前端速度那些事兒

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

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

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

    wudengzan 評(píng)論0 收藏0
  • H5直播答題并不難,看完這篇你也會(huì)

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

    hiyang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<