Преглед изворни кода

Add custom KMPDFPageAdapter, KMPDFPageView & KMPDFReaderView

cooperku_kdanmobile пре 5 година
родитељ
комит
987807781e

+ 64 - 0
src/main/java/com/kdanmobile/reader/adpage/AbstractPageAdapter.kt

@@ -0,0 +1,64 @@
+package com.kdanmobile.reader.adpage
+
+import android.content.Context
+import android.graphics.Point
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
+import com.kdanmobile.kmpdfkit.pdfcommon.FilePicker
+import com.kdanmobile.kmpdfkit.pdfcommon.KMPDFPageAdapter
+import com.kdanmobile.reader.R
+
+/**
+ * 客製化KMPDFPageAdapter
+ *
+ * 負責創建廣告頁面
+ */
+abstract class AbstractPageAdapter(
+        protected val context: Context,
+        private val filePickerSupport: FilePicker.FilePickerSupport,
+        private val kmpdfFactory: KMPDFFactory,
+        protected val adPageHelper: AdPageHelper
+): KMPDFPageAdapter(context, filePickerSupport, kmpdfFactory) {
+
+    /**
+     * 設定廣告內容
+     * 子類別可覆寫此方法以客製化不同視覺設計
+     */
+    abstract fun setupViewContent(position: Int, pageView: MyPageView)
+
+    /**
+     * 總頁數應包含廣告頁
+     */
+    override fun getCount(): Int {
+        return adPageHelper.getPageCount()
+    }
+
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
+        //  判斷頁面是否為廣告頁
+        val isAdPage = adPageHelper.isAdPage(position)
+        //  建立或讀取客製化KMPDFPageView
+        val pageView = if (convertView == null) {
+            MyPageView(context, filePickerSupport, kmpdfFactory, Point(parent?.width ?: 0, parent?.height ?: 0), isAdPage)
+        } else {
+            convertView as MyPageView
+        }
+        when (isAdPage) {
+            true -> {
+                //  設定KMPDFPageView
+                pageView.apply {
+                    adHeight = MyPageView.DEFAULT_AD_HEIGHT
+                    setupViewContent(position, this)
+                    isAdShowing = adPageHelper.isAdVisible(position)
+                }
+            }
+            false -> {
+                //  取得文件原始頁數
+                val page = adPageHelper.convertToRawPageIndex(position)
+                super.getView(page, pageView, parent)
+            }
+        }
+        return pageView
+    }
+}

+ 155 - 0
src/main/java/com/kdanmobile/reader/adpage/MyPageView.kt

@@ -0,0 +1,155 @@
+package com.kdanmobile.reader.adpage
+
+import android.content.Context
+import android.graphics.*
+import android.view.View
+import android.widget.RelativeLayout
+import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
+import com.kdanmobile.kmpdfkit.pdfcommon.*
+import com.kdanmobile.reader.utils.DensityUtil
+
+/**
+ * 客製化KMPDFPageView
+ *
+ * 用以處理廣告顯示
+ */
+class MyPageView(
+        context: Context,
+        filePickerSupport: FilePicker.FilePickerSupport,
+        kmpdfFactory: KMPDFFactory,
+        parentSize: Point,
+        //  此頁是否是廣告頁
+        private val isAdPage: Boolean
+) : KMPDFPageView(context, filePickerSupport, kmpdfFactory, parentSize) {
+
+    companion object {
+        //  預設廣告高度
+        const val DEFAULT_AD_HEIGHT = 450
+        //  預設廣告頁碼(建議為負值,避免錯誤書籤顯示)
+        const val AD_PAGE_NUMBER_ID = -99
+    }
+
+    //  廣告寬度(螢幕寬度)
+    val adWidth = DensityUtil.getScreenWidthPx(context)
+    //  廣告高度(執行期變化)
+    var adHeight = 0
+    //  不可見廣告的寬度
+    private val invisibleAdSize = PointF(adWidth.toFloat(), 1f)
+    //  不可見廣告的高度
+    private val invisibleAdRect = RectF(0f, 0f, invisibleAdSize.x, invisibleAdSize.y)
+    //  頁面的縮放值
+    private var viewScale = -1f
+
+    //  用來放置廣告的容器
+    val layout = RelativeLayout(context).also {
+        it.visibility = View.GONE
+        addView(it)
+    }
+
+    //  廣告是否可見
+    var isAdShowing = false
+        set(value) {
+            if (!isAdPage) return
+            if (field != value) {
+                field = value
+                val visibility = when (field) {
+                    true -> View.VISIBLE
+                    false -> View.INVISIBLE
+                }
+                layout.visibility = visibility
+                this.visibility = visibility
+                initPageSize = false
+            }
+        }
+    //  是否曾初始化
+    private var initPageSize = false
+
+    init {
+        //  如果是廣告
+        if (isAdPage) {
+            //  設定頁碼
+            mPageNumber = AD_PAGE_NUMBER_ID
+            //  廣告大小為不可見
+            super.setPage(page, invisibleAdSize, invisibleAdRect)
+            //  隱藏所有內容
+            for (index in 0 until childCount) {
+                getChildAt(index).visibility = View.INVISIBLE
+            }
+            //  設定透明背景
+            setBackgroundColor(Color.TRANSPARENT)
+        }
+    }
+
+    /**
+     * 處理長按事件(顯示Context Menu)
+     */
+    override fun openLongClickBlankContextMenu(view: View?) {
+        //  如果是廣告頁,則遮蔽長按事件
+        if (isAdPage) return
+        super.openLongClickBlankContextMenu(view)
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        //  如果不是廣告頁,則不執行以下指令
+        if (!isAdPage) return
+        //  在某些情況下,頁碼會被重置為0,因此需要重新設定(SDK bug?)
+        mPageNumber = AD_PAGE_NUMBER_ID
+
+        //  如果廣告可見
+        if (isAdShowing) {
+            //  廣告置中,大小固定為adWidth x adHeight
+            layout.layout(-x.toInt(), 0, (adWidth - x).toInt(), adHeight)
+            //  更新頁面高度
+            updateAdHeight()
+            //  更新內容大小
+            updateAdContentSize()
+        } else if (!initPageSize) {
+            super.setPage(page, invisibleAdSize, invisibleAdRect)
+            initPageSize = true
+        }
+    }
+
+    /**
+     * 取得頁面縮放值
+     */
+    private fun getPageViewScale(): Float {
+        return width / mSize.x.toFloat()
+    }
+
+    /**
+     * 更新頁面高度
+     */
+    private fun updateAdHeight() {
+        //  如果縮放未改變則不更新
+        val scale = getPageViewScale()
+        if (scale == viewScale) return
+        viewScale = scale
+        //  計算頁面大小
+        val modifyPageSize = PointF(adWidth.toFloat(), adHeight / viewScale)
+        val cropPageSize = RectF(0f, 0f, modifyPageSize.x, modifyPageSize.y)
+        //  設定頁面大小
+        setPage(page, modifyPageSize, cropPageSize)
+        //  隱藏廣告以外的所有內容
+        for (index in 0 until childCount) {
+            getChildAt(index).also {
+                if (it != layout) {
+                    it.visibility = View.INVISIBLE
+                }
+            }
+        }
+    }
+
+    /**
+     * 更新內容大小
+     */
+    private fun updateAdContentSize() {
+        for (i in 0 until layout.childCount) {
+            layout.getChildAt(i).apply {
+                if (left != 0 || top != 0 || right != adWidth || bottom != adHeight) {
+                    layout(0, 0, adWidth, adHeight)
+                }
+            }
+        }
+    }
+}

+ 84 - 0
src/main/java/com/kdanmobile/reader/adpage/MyReaderView.kt

@@ -0,0 +1,84 @@
+package com.kdanmobile.reader.adpage
+
+import android.content.Context
+import android.graphics.PointF
+import android.view.MotionEvent
+import com.kdanmobile.kmpdfkit.pdfcommon.KMPDFReaderView
+
+/**
+ * 客製化KMPDFReaderView
+ *
+ * 用以處理廣告點擊事件與修改當前頁面判斷
+ */
+open class MyReaderView(
+        context: Context,
+        private val adPageHelper: AdPageHelper
+) : KMPDFReaderView(context) {
+
+    /**
+     * 常數,拖曳容忍值
+     * 該值表示使用者累計拖曳未滿多少像素可視為未拖曳,避免裝置過於靈敏導致點擊困難
+     */
+    companion object {
+        private const val DRAG_DISTANCE_TOLERATION = 20f
+    }
+
+    //  點擊位置
+    private val touchPoint = PointF()
+    //  累計拖曳距離
+    private var dragDistance = 0f
+    //  是否剛點擊螢幕?
+    private var isJustTouch = false
+
+    override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+        //  若使用者剛點擊螢幕,忽略該事件,避免畫面跳動
+        if (mMode == Mode.Viewing && isJustTouch) {
+            isJustTouch = false
+            return false
+        }
+        return super.onScroll(e1, e2, distanceX, distanceY)
+    }
+
+    override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
+        //  僅閱覽模式需要判斷
+        val intercept = if (mMode == Mode.Viewing) {
+            //  按下螢幕
+            if (motionEvent.action == MotionEvent.ACTION_DOWN) {
+                //  使用者剛點擊螢幕
+                isJustTouch = true
+                //  設定點擊位置
+                touchPoint.set(motionEvent.x, motionEvent.y)
+                //  重設累計拖曳距離
+                dragDistance = 0f
+            }
+            //  計算累計拖曳距離
+            dragDistance += Math.abs(motionEvent.x - touchPoint.x) + Math.abs(motionEvent.y - touchPoint.y)
+            //  重設點擊位置
+            touchPoint.set(motionEvent.x, motionEvent.y)
+
+            //  遮蔽未超過容忍值之前的拖曳事件
+            dragDistance >= DRAG_DISTANCE_TOLERATION && motionEvent.action == MotionEvent.ACTION_MOVE
+        } else {
+            true
+        }
+        return intercept && super.onInterceptTouchEvent(motionEvent)
+    }
+
+    override fun getDisplayedViewIndex(): Int {
+        return adPageHelper.convertToRawPageIndex(mCurrent)
+    }
+
+    override fun setDisplayedViewIndex(i: Int, isRecordLastJumpPageNum: Boolean) {
+        super.setDisplayedViewIndex(adPageHelper.convertToPageIndex(i), isRecordLastJumpPageNum)
+    }
+
+    /**
+     * 覆寫原始的refresh方法
+     * 避免重整後當前頁面錯誤
+     */
+    override fun refresh(reflow: Boolean) {
+        val page = mCurrent
+        super.refresh(reflow)
+        super.setDisplayedViewIndex(page, false)
+    }
+}