Android自定义控件实例(1)——自定义控件之组合控件,包含书签的pdf阅读器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wjk343977868/article/details/54906225
前言
我们知道,在android端显示文档内容时,大多都是将文档转换为html页面,然后加载到webview中进行展示。这种展示方法好处在于可以结合html5、css和js做出非常美观的文档。但是对于html5、css和js不熟悉的人,可就有点蛋疼了。由于项目的需要,我基于github开源的AndroidTreeView和AndroidPdfViewer两个控件,通过组合形成了本文的“带书签的pdf”控件,通过点击书签,能够展开和收起书签,同时跳转到相应页面,也勉强够用了。

自定义View简介
常见的Android自定义View主要有两种类型:
  • 组合控件:通过组合现有的Android的基础控件,以达到复用的目的。比如试题控件(TextView+VideoGroup)和本文将要介绍的“带书签的PdfView”控件等,这种自定义View的难点在于程序的逻辑处理;
  • 完全自定义控件:继承自View、TextureView或SurfaceView,然后重写核心的回调方法,以View为例,按需复写其构造、onMeasure、onLayout、onTouchEvent、onDraw、onAttachedToWindow、onDetachedFromWindow等方法,这种自定义View的难点在于程序的设计、效率优化和排版。

MyPdfViewer实战
本文的开发环境为AndroidStudio2.2.2。先上一张效果图,如下:


1、新建项目:
     新建一个Android Phone and Tablet项目,命名为PDFExample。然后添加一个Android Library类库(Module),在这个库里实现我们的带书签的PdfViewer控件。

2、添加依赖
     在library类库的build.gradle中添加对AndroidTreeView和AndroidPdfViewer的依赖:
     compile 'com.github.barteksc:android-pdf-viewer:2.1.0'
     compile 'com.github.bmelnychuk:atv:1.2.+'

3、添加属性
     自定义带书签的控件的属性包括是否显示书签、书签宽度权重和内容宽度权重三个属性。如下:
<resources>
    <declare-styleable name="MyPDFView">
        <attr name="bookmark_visiable" format="boolean"/>
        <attr name="bookmark_weight" format="dimension"/>
        <attr name="content_weight" format="dimension"/>
    </declare-styleable>
</resources>

4、书签节点Holder的实现
     通过AndroidTreeView控件作者的介绍可知,自定义书签节点时需要继承TreeNode.BaseNodeViewHolder类,并重写createNodeView()方法。我们的书签节点由TextView和两个ImageView三部分构成,TextView展示书签的内容,第一个ImageView指示书签是展开还是收起。
     展示书签内容的代码在loadComplete()方法中,该方法调用getTree(),通过递归展示所有的书签。
java详细代码如下:
package com.***.library;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.github.barteksc.pdfviewer.PDFView;
import com.unnamed.b.atv.model.TreeNode;

public class BookMarkTreeItemHolder extends TreeNode.BaseNodeViewHolder<BookMarkTreeItemHolder.IconTreeItem> {
    private TextView tvValue;
    private ImageView arrowView;

    public BookMarkTreeItemHolder(Context context){
        super(context);
    }

    @Override
    public View createNodeView(final TreeNode node, final IconTreeItem value) {
        final LayoutInflater inflater = LayoutInflater.from(context);
        final View view = inflater.inflate(R.layout.layout_bookmark_node, null, false);
        tvValue = (TextView) view.findViewById(R.id.node_value);
        tvValue.setText(value.text);

        final ImageView iconView = (ImageView) view.findViewById(R.id.icon);
        iconView.setImageResource(R.drawable.bookmark);

        arrowView = (ImageView) view.findViewById(R.id.arrow_icon);
        if(value.isLeaf){
            arrowView.setVisibility(View.INVISIBLE);
        }

        return view;
    }

    @Override
    public void toggle(boolean active) {
        if(mNode.isLeaf()){//如果是叶节点,隐藏展开或收起图片
            arrowView.setVisibility(View.INVISIBLE);
        } else {
            arrowView.setVisibility(View.VISIBLE);
            if(active){
                arrowView.setRotation(90);
            } else {
                arrowView.setRotation(0);
            }
        }
    }

    @Override
    public int getContainerStyle() {
        return R.style.TreeNodeStyleCustom;
    }

    /**
    * 书签节点类
    */
    public static class IconTreeItem {
        public boolean isLeaf;//是否是叶节点
        public long pageIndex; //该节点所在页面
        public String text;//节点内容

        public IconTreeItem(boolean isLeaf, long pageIndex, String text) {
            this.isLeaf = isLeaf;
            this.pageIndex = pageIndex;
            this.text = text;
        }
    }
}

     书签节点布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minHeight="40dp">

    <ImageView
        android:id="@+id/arrow_icon"
        android:layout_width="23px"
        android:layout_height="23px"
        android:layout_alignParentLeft="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10px"
        android:src="@drawable/right_arrow" />

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignWithParentIfMissing="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10px"
        android:layout_toRightOf="@id/arrow_icon"
        android:src="@drawable/bookmark" />

    <TextView
        android:id="@+id/node_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/icon"
        android:textSize="16sp" />
</RelativeLayout>

5、自定义MyPDFViewer
     自定义MyPDFViewer,继承自LinearLayout,并实现com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener。代码如下:
package com.***.library;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;

import com.github.barteksc.pdfviewer.PDFView;
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
import com.shockwave.pdfium.PdfDocument;
import com.unnamed.b.atv.model.TreeNode;
import com.unnamed.b.atv.view.AndroidTreeView;

import java.io.File;
import java.util.List;

/**
* Created by JKWANG-PC on 2017/1/4.
*/

public class MyPDFViewer extends LinearLayout implements OnLoadCompleteListener {

    private LinearLayout bookMarkContainer;//书签布局
    private PDFView pdfView;
    private List<PdfDocument.Bookmark> bookMarks;
    private AndroidTreeView treeView;

    private boolean bookMarkVisiable = true;
    private int bookMarkWeight = 1;//书签的宽度,为权重值
    private int contentWeight = 3;//pdf文档的宽度,为权重值
    private String pdfPath;
    private String passWord;

    public MyPDFViewer(Context context) {
        this(context, null);
    }

    public MyPDFViewer(Context context, AttributeSet attrs) {
        super(context, attrs);

        setOrientation(HORIZONTAL);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyPDFViewer);
        bookMarkVisiable = typedArray.getBoolean(R.styleable.MyPDFViewer_bookmark_visiable, true);
        bookMarkWeight = typedArray.getInteger(R.styleable.MyPDFViewer_bookmark_weight, 1);
        contentWeight = typedArray.getInteger(R.styleable.MyPDFViewer_content_weight, 3);

        bookMarkContainer = new LinearLayout(getContext());
        bookMarkContainer.setOrientation(VERTICAL);

        pdfView = new PDFView(getContext(), null);

        //在代码中编写布局
        addView(bookMarkContainer);
        addView(pdfView);
    }

    public void load(){
        if(pdfPath.isEmpty() || pdfPath == null){
            return;
        }
        bookMarkContainer.setLayoutParams(new LayoutParams(0, LayoutParams.MATCH_PARENT, bookMarkWeight));
        bookMarkContainer.setVisibility(bookMarkVisiable ? VISIBLE : GONE);
        pdfView.setLayoutParams(new LayoutParams(0, LayoutParams.MATCH_PARENT, contentWeight));

        pdfView.fromFile(new File(pdfPath))
                .pages(null) // all pages are displayed by default
                .enableSwipe(true)
                .swipeHorizontal(false)
                .enableDoubletap(true)
                .defaultPage(0)
                .enableAnnotationRendering(true)
                .password(passWord)//pdf打开密码
                .scrollHandle(null)
                .onLoad(this)
                .load();
        pdfView.zoomTo(1.75f);
        pdfView.setMaxZoom(1.75f);
        pdfView.setMinZoom(1.75f);
    }

    public boolean isBookMarkVisiable() {
        return bookMarkVisiable;
    }

    public MyPDFViewer setBookMarkVisiable(boolean bookMarkVisiable) {
        this.bookMarkVisiable = bookMarkVisiable;
        return this;
    }

    public int getBookMarkWeight() {
        return bookMarkWeight;
    }

    public MyPDFViewer setBookMarkWeight(int bookMarkWeight) {
        this.bookMarkWeight = bookMarkWeight;
        return this;
    }

    public int getContentWeight() {
        return contentWeight;
    }

    public MyPDFViewer setContentWeight(int contentWeight) {
        this.contentWeight = contentWeight;
        return this;
    }

    public String getPdfPath() {
        return pdfPath;
    }

    public MyPDFViewer setPdfPath(String pdfPath) {
        this.pdfPath = pdfPath;
        return this;
    }

    public String getPassWord() {
        return passWord;
    }

    public MyPDFViewer setPassWord(String passWord) {
        this.passWord = passWord;
        return this;
    }

    @Override
    public void loadComplete(int nbPages) {
        Log.e("tag", "load complete");
        bookMarks = pdfView.getTableOfContents();

        TreeNode root = TreeNode.root();
        for (int i = 0, n = bookMarks.size(); i < n; ++i) {
            TreeNode parent = new TreeNode(new BookMarkTreeItemHolder.IconTreeItem(bookMarks.get(i).getChildren().size() > 0 ? false : true, bookMarks.get(i).getPageIdx(), bookMarks.get(i).getTitle()))
                    .setViewHolder(new BookMarkTreeItemHolder(getContext()));
            getTree(bookMarks.get(i).getChildren(), parent);//递归获取所有书签
            root.addChild(parent);
        }
        treeView = new AndroidTreeView(getContext(), root);
        treeView.setDefaultAnimation(true);
        treeView.setDefaultViewHolder(BookMarkTreeItemHolder.class);
        treeView.setDefaultContainerStyle(R.style.TreeNodeStyleCustom);
        treeView.setDefaultNodeClickListener(nodeClickListener);//绑定书签节点点击事件

        bookMarkContainer.addView(treeView.getView());
    }

    private TreeNode.TreeNodeClickListener nodeClickListener = new TreeNode.TreeNodeClickListener() {
        @Override
        public void onClick(TreeNode node, Object value) {
            BookMarkTreeItemHolder.IconTreeItem item = (BookMarkTreeItemHolder.IconTreeItem) value;
            pdfView.jumpTo((int) item.pageIndex);//点击书签节点时,跳转到指定页面
        }
    };

    //递归获取所有书签节点
    public void getTree(List<PdfDocument.Bookmark> bookmarks, TreeNode parent) {
        for (int i = 0, n = bookmarks.size(); i < n; ++i) {
            TreeNode child = new TreeNode(new BookMarkTreeItemHolder.IconTreeItem(bookmarks.get(i).hasChildren() ? false : true, bookmarks.get(i).getPageIdx(), bookmarks.get(i).getTitle()))
                    .setViewHolder(new BookMarkTreeItemHolder(getContext()));
            if (bookmarks.get(i).hasChildren()) {
                getTree(bookmarks.get(i).getChildren(), child);
            }
            parent.addChild(child);
        }
    }
}

6、MyPDFViewer应用
     在app的MainActivity调用自定义的MyPDFViewe控件,代码如下:
public class MainActivity extends AppCompatActivity {

    private MyPDFViewer pdfView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pdfView=(MyPDFViewer)findViewById(R.id.pdfView);
        pdfView.setPdfPath(Environment.getExternalStorageDirectory().getPath() + "/Download/疯狂Android讲义 第3版.PDF")
                //.setPassWord("1234")
                .setBookMarkWeight(2)
                .setContentWeight(5)
                .load();

        Log.e("load:","load complete");
    }
}

至此,关于自定义MyPDFViewer组合控件就完成了,非常简单,但是比较实用。

参考资料:

猜你喜欢

转载自blog.csdn.net/wjk343977868/article/details/54906225