メディアコンテンツを衚瀺するためのAndroidギャラリヌラむブラリの䜜成方法



こんにちは、Habr 少し前、冒険、新しいプロゞェクト、技術を探しお、私は ロボットになりたした Redmadrobotに定䜏したした。 怅子、モニタヌ、Macbook、そしおりォヌムアップ甚の小さな内郚プロゞェクトを手に入れたした。 プロゞェクトで䜿甚するメディアコンテンツを衚瀺するには、自己蚘述ラむブラリを完成させお公開する必芁がありたした。 この蚘事では、1週間でタッチむベントを理解し、オヌプン゜ヌスになり、Android SDKのバグを芋぀け、ラむブラリを公開する方法を説明したす。


開始する


ストアアプリの重芁な機胜の1぀は、商品やサヌビスの動画や写真をあらゆる偎面から衚瀺できるこずです。 車茪を再発明したくなかったので、私たちに合った完成したラむブラリを探しに行きたした。


ナヌザヌが次のこずができるように、このような゜リュヌションを芋぀けるこずを蚈画したした。



私たちが芋぀けたものは次のずおりです。



芋぀かったラむブラリはどれも芁件を完党に満たしおいないため、独自のラむブラリを䜜成する必芁がありたした。


図曞通を実珟したす


必芁な機胜を取埗するために、他のラむブラリから既存の゜リュヌションを完成させたした。 圌らは䜕が起こったのか控えめな名前のAndroidギャラリヌを䞎えるこずにしたした。


機胜を実装したす


写真の衚瀺ずズヌム
写真を衚瀺するために、PhotoViewラむブラリを䜿甚したした。これは、すぐに䜿甚できるスケヌリングをサポヌトしおいたす。


ビデオを芋る
ビデオを芋るために、圌らはMediaPagerAdapterで再利甚されるExoPlayerを取りたした 。 ナヌザヌが初めおビデオを開くず、ExoPlayerが䜜成されたす。 別の芁玠に切り替えるずキュヌに入れられるため、次にビデオを開始するずきに、既に䜜成されおいるExoPlayerむンスタンスが䜿甚されたす。 これにより、芁玠間の移行がよりスムヌズになりたす。


メディアコンテンツのスクロヌル
ここでは、FrescoImageViewerのMultiTouchViewPagerを䜿甚したしたが、これはマルチタッチむベントをむンタヌセプトしないため、ゞェスチャを远加しお画像を拡倧瞮小するこずができたした。


スワむプしお閉じたす
PhotoViewは、スワむプを非衚瀺にしおデバりンスをサポヌトしおいたせん画像を拡倧たたは瞮小したずきに元の画像サむズを埩元したす。
これをどのように凊理したかを次に瀺したす。


タッチむベントを孊習しお、スワむプしお実装したす


スワむプをサポヌトしお华䞋する前に、タッチむベントの仕組みを理解する必芁がありたす。 ナヌザヌが画面に觊れるず、珟圚のアクティビティでdispatchTouchEvent(motionEvent: MotionEvent)メ゜ッドが呌び出され、 MotionEvent.ACTION_DOWNがMotionEvent.ACTION_DOWNたす。 このメ゜ッドは、むベントの運呜を決定したす。 タッチ凊理のためにmotionEventをonTouchEvent(motionEvent: MotionEvent)にonTouchEvent(motionEvent: MotionEvent)か、View階局で䞊から䞋にさらにプッシュできたす。 ACTION_UPより前のむベントおよび/たたは埌続のむベントに関心があるビュヌは、trueを返したす。


その埌、ゞェスチャがACTION_UPむベントで終了するか、芪ViewGroupが制埡をACTION_UPするたで、珟圚のゞェスチャのすべおのむベントがこのビュヌに分類されたすその埌、 ACTION_CANCELEDむベントが衚瀺されたす。 むベントがビュヌ階局党䜓をonTouchEvent(motionEvent: MotionEvent)し、誰も関心を持たなかった堎合、 onTouchEvent(motionEvent: MotionEvent)のアクティビティonTouchEvent(motionEvent: MotionEvent)に戻りたす。



Androidギャラリヌラむブラリでは、最初のACTION_DOWNむベントがACTION_DOWN dispatchTouchEvent()に到達し、そこでmotionEvent trueを返すonTouch()実装に枡されたす。 さらに、すべおのむベントは、次のいずれかになるたで同じチェヌンを通過したす。



onInterceptTouchEvent(motionEvent: MotionEvent)メ゜ッドのonInterceptTouchEvent(motionEvent: MotionEvent)のみがむベントをむンタヌセプトできたす。 ビュヌがMotionEventに関心がある堎合でも、むベント自䜓は前のViewGroupチェヌン党䜓のdispatchTouchEvent(motionEvent: MotionEvent)通過したす。 したがっお、芪は垞に子䟛を「芋る」。 すべおの芪ViewGroupはむベントをむンタヌセプトし、 onInterceptTouchEvent(motionEvent: MotionEvent)でtrueを返すこずができ、すべおの子MotionEvent.ACTION_CANCELはonTouchEvent(motionEvent: MotionEvent)でonTouchEvent(motionEvent: MotionEvent)を受け取りたす。


䟋ナヌザヌがRecyclerViewの芁玠に指を圓おるず、同じ芁玠でむベントが凊理されたす。 しかし、指を䞊䞋に動かし始めるずすぐに、RecyclerViewはむベントをむンタヌセプトし、スクロヌルが開始され、ビュヌはACTION_CANCELむベントを受け取りたす。



Androidギャラリヌで、VerticalDragLayoutは、スワむプでむベントをむンタヌセプトしお、ViewPagerでスクロヌルできたす。 ただし、ViewはrequestDisallowInterceptTouchEvent(true)メ゜ッドを呌び出すこずにより、芪がむベントをrequestDisallowInterceptTouchEvent(true)するのを防ぐこずができたす。 これは、ビュヌがそのようなアクションを実行する必芁がある堎合に必芁になる可胜性があり、そのむンタヌセプトは芪にずっお望たしくありたせん。


たずえば、プレヌダヌのナヌザヌが特定の時間たでトラックをスキップした堎合。 芪ViewPagerが氎平スクロヌルをむンタヌセプトした堎合、次のトラックぞの移行がありたす。


スワむプを無芖しお凊理するために、VerticalDragLayoutを䜜成したしたが、PhotoViewからタッチむベントを受け取りたせんでした。 これが起こる理由を理解するには、PhotoViewでタッチむベントがどのように凊理されるかを把握する必芁がありたした。


凊理順序


  1. VerticalDragLayoutのMotionEvent.ACTION_DOWNの堎合、 interceptTouchEvent()トリガヌされ、falseを返したす。 このViewGroupは垂盎ACTION_MOVEのみに関心がありたす。 ACTION_MOVE方向はdispatchTouchEvent()で定矩され、その埌、むベントはsuper.dispatchTouchEvent()メ゜ッドに枡され、そこでむベントはVerticalDragLayoutのinterceptTouchEvent()実装に枡されたす。



  2. ACTION_DOWNむベントがonTouch()メ゜ッドに到達するず、ビュヌはむベント管理をむンタヌセプトする機胜を奪いたす。 埌続のすべおのゞェスチャヌむベントは、 interceptTouchEvent()メ゜ッドに分類されたせん。 コントロヌルをむンタヌセプトする機胜は、ゞェスチャが完了した堎合、たたは画像の巊右の境界で氎平方向のACTION_MOVEした堎合にのみ芪に䞎えられたす。
     if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) { if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f) || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) { if (parent != null) { parent.requestDisallowInterceptTouchEvent(false); } } } else { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } 



    PhotoViewでは、芪が氎平のACTION_MOVE堎合にのみ制埡を取埗でき、スワむプしお閉じるず垂盎のACTION_MOVEであるため、VerticalDragLayoutはむベントコントロヌルをむンタヌセプトしおゞェスチャヌを実装できたせん。 これを修正するには、垂盎のACTION_MOVE堎合に制埡をむンタヌセプトする機胜を远加する必芁がありたす。

     if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) { if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f) || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f) || mVerticalScrollEdge == VERTICAL_EDGE_BOTH || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f) || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) { if (parent != null) { parent.requestDisallowInterceptTouchEvent(false); } } } else { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } 

    さお、最初の垂盎の堎合、 ACTION_MOVE PhotoViewは芪をむンタヌセプトする機䌚を䞎えたす



    次のACTION_MOVEはVerticalDragLyoutでむンタヌセプトされ、 ACTION_CANCELむベントは子ビュヌに飛びたす



    他のすべおのACTION_MOVEは、暙準チェヌンに沿っおVerticalDragLayoutに移動したす。 ViewGroupが子ビュヌからむベントを制埡した埌、子ビュヌが制埡を取り戻せないこずが重芁です。



    そこで、PhotoViewラむブラリのサポヌトを終了するためにスワむプを実装したした。 ラむブラリでは、PhotoViewの倉曎された゜ヌスを別のモゞュヌルで取り出しお䜿甚し、元のPhotoViewリポゞトリにマヌゞリク゚ストを䜜成したした。

    PhotoViewにバむンド解陀を実装したす


    デバりンスずは、画像がその限界を超えおスケ​​ヌリングされた堎合の蚱容可胜なスケヌルのアニメヌション埩元であるこずを思い出しおください。



    PhotoViewではそのような可胜性はありたせんでした。 しかし、私たちは他の誰かのオヌプン゜ヌスを掘り始めたのに、なぜそこで停止するのですか PhotoViewでは、ズヌム制限を蚭定できたす。 最初は、これは最小-x1および最倧-x3です。 画像はこれらの制限を超えるこずはできたせん。



     @Override public void onScale(float scaleFactor, float focusX, float focusY) { if ((getScale() < mMaxScale || scaleFactor < 1f) && (getScale() > mMinScale || scaleFactor > 1f)) { if (mScaleChangeListener != null) { mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); } mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); //      checkAndDisplayMatrix(); } } 

    はじめに、「最小倀に達したずきのスケヌリングの犁止」条件を削陀するこずにしたした。単にgetScale() > mMinScale || scaleFactor > 1fずいう条件をgetScale() > mMinScale || scaleFactor > 1fしたした。 getScale() > mMinScale || scaleFactor > 1f 。 そしお、突然...



    デブンを獲埗したした どうやら、これはラむブラリの䜜成者がラむブラリを2回安党に再生するこずを決定し、デバりンスずスケヌリングの制限の䞡方を行ったために発生したようです。 onTouchむベントの実装、぀たりMotionEvent.ACTION_UP堎合、ナヌザヌが最倧/最小よりも倧きい/小さい堎合、 AnimatedZoomRunnableが起動され、画像が元のサむズに戻りたす。


     @Override public boolean onTouch(View v, MotionEvent ev) { boolean handled = false; switch (ev.getAction()) { case MotionEvent.ACTION_UP: // If the user has zoomed less than min scale, zoom back // to min scale if (getScale() < mMinScale) { RectF rect = getDisplayRect(); if (rect != null) { v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY())); handled = true; } } else if (getScale() > mMaxScale) { RectF rect = getDisplayRect(); if (rect != null) { v.post(new AnimatedZoomRunnable(getScale(), mMaxScale, rect.centerX(), rect.centerY())); handled = true; } } break; } } 

    スワむプで閉じるだけでなく、ラむブラリの゜ヌスでPhotoViewを確定し、元のPhotoViewにデバりンスを「远加」しおプルリク゚ストを䜜成したした。


    PhotoViewの突然のバグを修正


    PhotoViewには非垞に厄介なバグがありたす。 ナヌザヌがダブルタップで画像を拡倧したい堎合 圌はおんかんの発䜜を起こしおいる 画像のスケヌリングが開始され、垂盎に180床反転できたす。 このバグは、CyanなどのGoogle Playの人気のあるアプリケヌションでも芋぀かりたす。



    長い怜玢の埌、このバグをロヌカラむズしたした時々、負のscaleFactorがスケヌリングのための画像スケヌリングのために入力行列に䞎えられ、それにより画像が反転したす。


    CustomGestureDetector


     @Override public boolean onScale(ScaleGestureDetector detector) { //  -   scaleFactor < 0 //      float scaleFactor = detector.getScaleFactor(); if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) return false; //    scaleFactor   callback //      mListener.onScale(scaleFactor, detector.getFocusX(), detector.getFocusY()); return true; } 

    Android ScaleGestureDetectorからスケヌリングするには 、次のように蚈算されるscaleFactor を取埗したす。


     public float getScaleFactor() { if (inAnchoredScaleMode()) { // Drag is moving up; the further away from the gesture // start, the smaller the span should be, the closer, // the larger the span, and therefore the larger the scale final boolean scaleUp = (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) || (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan)); final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR); return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff); } return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } 

    デバッグログでこのメ゜ッドをオヌバヌラむドするず、負のscaleFactorが取埗される倉数の特定の倀を远跡できたす。


     mEventBeforeOrAboveStartingGestureEvent is true; SCALE_FACTOR is 0.5; mCurrSpan: 1075.4398; mPrevSpan 38.867798; scaleUp: false; spanDiff: 13.334586; eval result is -12.334586 

    spanDiffにSCALE_FACTOR == 0.5を掛けるこずにより、この問題を解決しようずした疑いがありたす。 しかし、mCurrSpanずmPrevSpanの違いが3倍以䞊の堎合、この゜リュヌションは圹に立ちたせん。 このバグのチケットはすでに開始されおいたすが、ただ修正されおいたせん。
    束葉杖 この問題の最も簡単な解決策は、負のscaleFactor倀を単玔にスキップするこずです。 実際には、ナヌザヌは、画像が通垞よりもわずかに滑らかにズヌムされないこずがありたす。


    結論の代わりに


    プルリク゚ストの運呜


    ロヌカルで修正を行い、PhotoViewで最埌のプルリク゚ストを䜜成したした。 いく぀かのPRが1幎間ハングしおいるずいう事実にもかかわらず、PRがmasterブランチに远加され、PhotoViewの新しいリリヌスでさえリリヌスされたした。 その埌、Androidギャラリヌからロヌカルモゞュヌルを切り取り、公匏のPhotoView゜ヌスを取埗するこずにしたした。 これを行うには、バヌゞョン2.1.3で PhotoViewに远加されたAndroidXのサポヌトを远加する必芁がありたした。


    ラむブラリの堎所


    ここでAndroidギャラリヌラむブラリの゜ヌスコヌド https://github.com/redmadrobot-spb/android-gallery を䜿甚手順ずずもに探したす。 たた、サポヌトラむブラリを匕き続き䜿甚するプロゞェクトをサポヌトするために、 android-gallery-deprecatedの別個のバヌゞョンを䜜成したした。 ただし、サポヌトラむブラリは1幎でカボチャになるので泚意しおください


    次は䜕ですか


    ラむブラリは完党に私たちに合っおいたすが、開発の過皋で新しいアむデアが生たれたした。 それらのいく぀かを次に瀺したす。


    • 個別のFragmentDialogだけでなく、任意のレむアりトでラむブラリを䜿甚する機胜。
    • UIをカスタマむズする機胜。
    • GildeずExoPlayerを眮き換える機胜。
    • ViewPagerの代わりに䜕かを䜿甚する機胜。

    参照資料



    UPD


    蚘事の執筆䞭に、 FrescoImageViewerの開発者からの同様のラむブラリ が登堎したした 。 トランゞションアニメヌションのサポヌトが远加されたしたが、これたでのずころビデオのサポヌトしかありたせん。 :)

Source: https://habr.com/ru/post/J433076/


All Articles