Android开发--MVP demo+Jsoup在线小说阅读器(一)

发布于:2023-07-04 ⋅ 阅读:(168) ⋅ 点赞:(0)

因为最近身体不好又是偷懒了一阵子没有更新…这次带来的是一个在线的小说阅读器.目前已经实现了基本的功能,完成了大概的框架,剩余的部分慢慢来更新。先放上源码github https://github.com/CallMeSp/ToRead_MVP.git 求star。里面也有这个项目没有应用mvp结构的源码可以用来对比一下。
最近看了MVP框架,所以这个项目也采用了mvp框架,参考了mvp入门解析浅谈mvp入门由于经验不够,有些粗糙。还使用了picasso图片加载库,Jsoup来实现网页解析功能。先看一下代码结构
代码结构
然后看一些效果图。
这里写图片描述
这里写图片描述这里写图片描述这里写图片描述
这就是用了MVP结构的代码结构了,明显感觉就是类的类别明显增多了,不过解耦性明显增强了,最大程度分离了view的交互和model的逻辑处理,view和model之间则通过presenter来沟通。
不过这又产生了一个问题:
这样一个view和一个presenter对应一个界面,要是一个完整的项目,界面肯定不少,那和以view和presenter会暴增,这样怎么办呢?
利用组合思想。V和P是一一对应的,但是,我们可以把通用的VP提取出来。一个Activity implements 多个View,然后利用组合包含几个P。举个例子。有个LoginPresenter。我们现在要在登陆页面中用到,另外在一个回复页面,也需要做个快速登陆功能。那么我们可能需要在LoginActivity和ReplyActivity中都包含这个LoginPresenter,两个Activity都各自去实现LoginView。可能ReplyActivity还有其他功能,他还要包含自己的ReplyPresenter,并实现自己的ReplyView。
利用组合来实现Presenter的复用,这个是MVP的优雅之一。但是别忘了不要持有View实例,记得detach。
然后言归正传,这个项目功能主要的实现依靠的是Jsoup的解析功能。来看一下bookbiz中根据搜索的书名来获取书籍列表的这一段。

@Override
    public void showbookslist(final String searchname){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    books.clear();
                    Document doc = Jsoup.connect("http://so.37zw.com/cse/search?q=" + searchname + "&click=1&s=2041213923836881982&nsid=")
                            .get();
                    Elements items=doc.select("div.game-legend-a");
                    for (Element Item : items) {
                        Log.e("0","Item:"+Item);
                        String title=Item.select("h3").text();
                        String detail=Item.select("p.result-game-item-desc").text();
                        String ur=Item.select("div.game-legend-a").attr("onclick");
                        ur=ur.substring(17, ur.length() - 1);
                        String writer=Item.select("p.result-game-item-info-tag").first().text();
                        String IMG=Item.select("img").attr("src");
                        book mybook=new book();
                        mybook.setBook_name(title);
                        mybook.setBook_writter(writer);
                        mybook.setBook_details(detail);
                        mybook.setBook_cover(IMG);
                        mybook.setContenturl(ur);
                        books.add(mybook);
                    }
                    presenter.updatelist(books);
                } catch (IOException e){

                    e.printStackTrace();
                }
            }
        }).start();
    }

jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操作方法来取出和操作数据。
jsoup的主要功能如下:
从一个URL,文件或字符串中解析HTML;
使用DOM或CSS选择器来查找、取出数据;
可操作HTML元素、属性、文本;此处运用的就是使用DOM选择器来查找和取出数据。本来相用正则来自己解析的..想想..还是算了吧..
http://www.open-open.com/jsoup/ 附上jsoup开发中文文档。里面讲的也很详细。大家也可以写写demo来测试一下。下面看一下picasso的应用:

Picasso.with(myholder.itemView.getContext())
                .load(mybook.get(position).getBook_cover())
                .centerInside()
                .fit()
                .into(myholder.bookcover);

怎么样是不是很简洁…其实然后看看我原来自己没有用这个库自己实现的:

private void DoGetbitmap() {

        new Thread(new Runnable() {
            @Override
            public void run() {

                for(int i=0;i<search_title_list.size();i++) {
                    Log.e("0","url;"+search_bitmapurl.get(i));
                    HttpGet httPost = new HttpGet(search_bitmapurl.get(i));
                    HttpClient client = new DefaultHttpClient();
                    // 请求超时
                    client.getParams().setParameter(
                            CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
                    // 读取超时
                    client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
                            10000);
                    try {
                        HttpResponse httpResponse = client.execute(httPost);
                        byte[] bytes = new byte[1024];
                        bytes = EntityUtils.toByteArray(httpResponse.getEntity());
                        bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

                        search_bookvover_list.add(bitmap);
                    } catch (IOException e) {
                        Log.e("0", "fail get bitmap");
                        e.printStackTrace();
                    }
                    Log.e("0", "success to get bitmap");
                }
                Log.e("0", "success to get bitmaps");
                Message.obtain(mhandeler,1).sendToTarget();
            }
        }).start();
    }

得设置各种Httpget、httpclient、然后害的response转bytes转bitmap然后再设置imageview简直蠢到爆啊而且性能还很low。写到这里想到了最近也正在学习retrofit和rxjava。学完后会对demo中的网络请求和线程操作重新更好的处理一下。demo中还有一个亮点是在长按item的时候会弹出一个菜单,而且此时背景会虚化,这也是结合了前一阵的所学算是活学活用吧。来看一下代码:

package com.sp.areader.view.fragment;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;

import com.sp.areader.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhaoshuang on 16/8/29.
 * 弹出动画的popupwindow
 */
public class HintPopupWindow {

    private Activity activity;
    private WindowManager.LayoutParams params;
    private boolean isShow;
    private WindowManager windowManager;
    private ViewGroup rootView;
    private ViewGroup linearLayout;

    private final int animDuration = 250;//动画执行时间

    /**
     * @param contentList 点击item的内容文字
     * @param clickList 点击item的事件
     * 文字和click事件的list是对应绑定的
     */
    public HintPopupWindow(Activity activity, List<String> contentList, List<View.OnClickListener> clickList){
        this.activity = activity;
        windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
        initLayout(contentList, clickList);
    }

    /**
     * @param contentList 点击item内容的文字
     * @param clickList 点击item的事件
     */
    public void initLayout(List<String> contentList, List<View.OnClickListener> clickList){

        //这是根布局
        rootView = (ViewGroup) View.inflate(activity, R.layout.item_root_hintpopupwindow, null);
        linearLayout = (ViewGroup) rootView.findViewById(R.id.linearLayout);

        //格式化点击item, 将文字和click事件一一绑定上去
        List<View> list = new ArrayList<>();
        for(int x=0; x<contentList.size(); x++){
            View view = View.inflate(activity, R.layout.item_hint_popupwindow, null);
            TextView textView = (TextView) view.findViewById(R.id.tv_content);
            View v_line = view.findViewById(R.id.v_line);
            textView.setText(contentList.get(x));
            linearLayout.addView(view);
            list.add(view);
            if(x == 0){
                v_line.setVisibility(View.INVISIBLE);
            }else{
                v_line.setVisibility(View.VISIBLE);
            }
        }
        for (int x=0; x<list.size(); x++){
            list.get(x).setOnClickListener(clickList.get(x));
        }

        //这里给你根布局设置背景透明, 为的是让他看起来和activity的布局一样
        params = new WindowManager.LayoutParams();
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.MATCH_PARENT;
        params.format = PixelFormat.RGBA_8888;//背景透明
        params.gravity = Gravity.LEFT | Gravity.TOP;

        //当点击根布局时, 隐藏
        rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                gonePopupWindow();
            }
        });

        rootView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //如果是显示状态那么隐藏视图
                if(keyCode == KeyEvent.KEYCODE_BACK && isShow) gonePopupWindow();
                return isShow;
            }
        });
    }

    /**
     * 弹出选项弹窗
     * @param locationView 默认在该view的下方弹出, 和popupWindow类似
     */
    public void showPopupWindow(View locationView){
        try {
            //这个步骤是得到该view相对于屏幕的坐标, 注意不是相对于父布局哦!
            int[] arr = new int[2];
            locationView.getLocationOnScreen(arr);
            linearLayout.measure(0, 0);//为view申请占 int,int大小的控件.若与实际大小不符合则会自动计算。
            Rect frame = new Rect();
            activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);//得到状态栏高度
            float x = arr[0] + locationView.getWidth() - linearLayout.getMeasuredWidth();
            //float y = arr[1] - frame.top + locationView.getHeight();
            float y = arr[1] - frame.top;
            linearLayout.setX(x);
            linearLayout.setY(y+50);

            /*捕获当前activity的布局视图, 因为我们要动态模糊, 所以这个布局一定要是最新的,
            *这样我们把模糊后的布局盖到屏幕上时, 才能让用户感觉不出来变化*/
            View decorView = activity.getWindow().getDecorView();
            Bitmap bitmap = getBitmapByView(decorView);//这里是将view转成bitmap
            setBlurBackground(bitmap);//这里是模糊图片, 这个是重点我会单独讲的, 因为效率很重要啊!!!

            //这里就是使用WindowManager直接将我们处理好的view添加到屏幕最前端
            windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
            windowManager.addView(rootView, params);

            //这一步就是有回弹效果的弹出动画, 我用属性动画写的, 很简单
            showAnim(linearLayout, 0, 1, animDuration, true);

            //视图被弹出来时得到焦点, 否则就捕获不到Touch事件
            rootView.setFocusable(true);
            rootView.setFocusableInTouchMode(true);
            rootView.requestFocus();
            rootView.requestFocusFromTouch();

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 得到bitmap位图, 传入View对象
     */
    public static Bitmap getBitmapByView(View view) {

        Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
        view.draw(new Canvas(bitmap));
        return bitmap;
    }

    private void setBlurBackground(Bitmap bitmap) {

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 3, bitmap.getHeight() / 3, false);
        Bitmap blurBitmap = getBlurBitmap(activity, scaledBitmap, 5);
        rootView.setAlpha(0);
        rootView.setBackgroundDrawable(new BitmapDrawable(blurBitmap));
        alphaAnim(rootView, 0, 1, animDuration);
    }

    public static Bitmap getBlurBitmap(Context context, Bitmap bitmap, int radius) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return blurBitmap(context, bitmap, radius);
        }
        return bitmap;
    }

    /**
     * android系统的模糊方法
     * @param bitmap 要模糊的图片
     * @param radius 模糊等级 >=0 && <=25
     */
    public static Bitmap blurBitmap(Context context, Bitmap bitmap, int radius) {

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
            //Let's create an empty bitmap with the same size of the bitmap we want to blur
            Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
            //Instantiate a new Renderscript
            RenderScript rs = RenderScript.create(context);
            //Create an Intrinsic Blur Script using the Renderscript
            ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            //Create the Allocations (in/out) with the Renderscript and the in/out bitmaps
            Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
            Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
            //Set the radius of the blur
            blurScript.setRadius(radius);
            //Perform the Renderscript
            blurScript.setInput(allIn);
            blurScript.forEach(allOut);
            //Copy the final bitmap created by the out Allocation to the outBitmap
            allOut.copyTo(outBitmap);
            //recycle the original bitmap
            bitmap.recycle();
            //After finishing everything, we destroy the Renderscript.
            rs.destroy();
            return outBitmap;
        }else{
            return bitmap;
        }
    }

    public void gonePopupWindow(){
        goneAnim(linearLayout, 0.95f, 1, animDuration /3, true);
        isShow = false;
    }

    public WindowManager.LayoutParams getLayoutParams(){
        return params;
    }

    public ViewGroup getLayout(){
        return linearLayout;
    }

    /**
     * popupwindow是否是显示状态
     */
    public boolean isShow(){
        return isShow;
    }

    private void alphaAnim(final View view, int start, int end, int duration){

        ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(duration);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                view.setAlpha(value);
            }
        });
        va.start();
    }

    private void showAnim(final View view, float start, final float end, int duration, final boolean isWhile) {

        ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(duration);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                view.setPivotX(view.getWidth());//设置缩放轴心点。以view为坐标
                view.setPivotY(0);
                view.setScaleX(value);
                view.setScaleY(value);
                Log.e("0","value="+value);
            }
        });
        va.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (isWhile) showAnim(view, end, 0.95f, animDuration / 3, false);
            }
        });
        va.start();
    }

    public void goneAnim(final View view, float start, final float end, int duration, final boolean isWhile){

        ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(duration);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                view.setPivotX(view.getWidth());
                view.setPivotY(0);
                view.setScaleX(value);
                view.setScaleY(value);
            }
        });
        va.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {

                if(isWhile){
                    alphaAnim(rootView, 1, 0, animDuration);
                    goneAnim(view, end, 0f, animDuration, false);
                }else{
                    try {
                        windowManager.removeView(rootView);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });
        va.start();
    }
}

代码里面重要的功能都有注释就不用多说了吧。其它的就是各种逻辑的处理了,在各个activity间跳转,也是很简单,想下载demo的拉到最上面点进我的github来下载。这篇博客就到这吧,才疏学浅也写不出什么长篇大论。
立个flag:
1.完善小说缓存下载功能,要求实现断点重连后台下载。
2.网络请求用retrofit改善
3.线程的处理用rxjava改善

本文含有隐藏内容,请 开通VIP 后查看

微信公众号

今日签到

点亮在社区的每一天
去签到