开源项目学习(微阅)

概述

今天开始学习一个新的项目,微阅,先看看效果图

这里写图片描述

这里写图片描述

然后我们看看项目的组织结构

这里写图片描述

这个项目也是采用MVP模式开发的,api包中是访问数据的接口,相当于M层,presenter包中相当于P层,调用api中的接口去访问数据,然后交给View层显示。

笔记

更改导航栏的颜色(5.0以上才支持),效果图如下

这里写图片描述

相关代码如下

  //更改底部导航栏的颜色,只有版本大于21,也就是5.0才支持
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (SharePreferenceUtil.isChangeNavColor(this))
                getWindow().setNavigationBarColor(vibrantColor);
            else
                getWindow().setNavigationBarColor(Color.BLACK);//黑色
        }

FloatingActionButton

这里写图片描述

向上滑动消失,向下滑动出现

先看布局文件

 <android.support.design.widget.FloatingActionButton
        android:id="@+id/fabButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        //按钮位置
        android:layout_gravity="bottom|end"
        //边距
        android:layout_margin="10dp"
        //按钮图片
        android:src="@drawable/ic_arrow_upward_white_24px"
        //按钮背景颜色
        app:backgroundTint="@color/colorAccent"
        //边框宽度,最好设置上
        app:borderWidth="0dp"
        //Z方向高度
        app:elevation="6dp"
        //按钮尺寸
        app:fabSize="normal"
//    自定义动作   app:layout_behavior="name.caiyao.microreader.ui.view.ScrollAwareFABBehavior"
//按下以后Z方向的平移距离
        app:pressedTranslationZ="12dp"
        //按下波纹的颜色
        app:rippleColor="#33728dff" />

接下来看看这个自定义的动作

public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
    //为了能在xml中使用
    public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
        super();
    }
    // 指明我们希望处理垂直方向的滚动事件。
    // 滚动事件同样是由本类处理,见下面的onNestedScroll()方法。
    @Override
    public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                       final View directTargetChild, final View target, final int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
                || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }
    // 检查Y轴的距离,决定是显示还是隐藏FAB。
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        //向上滑动时隐藏FAB
        if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
            child.hide();
            //向下滑动时显示FAB
        } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
            child.show();
        }
    }
}

更改状态栏颜色
首先说明,只有4.4及其以上版本才支持更改状态栏颜色,比如半透明或者全透明,也就是我们所说的沉浸式状态栏。在4.4之前状态栏一直是黑色的,在4.4中带来了 windowTranslucentStatus 这一特性,因此可以实现给状态栏设置颜色,大概代码如下

 public static void setColor(Activity activity, int color, int statusBarAlpha) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //5.0以上提供setStatusBarColor这个api设置状态栏颜色
            activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha));
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            // 生成一个状态栏大小的矩形
            View statusView = createStatusBarView(activity, color, statusBarAlpha);
            // 添加 statusView 到布局
            //获取decorView,其实是一个framelayout
            ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
            decorView.addView(statusView);
            setRootView(activity);
        }
    }

夜间模式
在2016年的2月24日,google的Android开发团队发布了:

compile 'com.android.support:appcompat-v7:23.2.0'

加入的新的东西,AppCompat DayNight theme和 Bottom Sheets。

AppCompat DayNight theme
这里写图片描述

先关代码如下

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {//必须要判断,因为当设置了模式以后,savedInstanceState就不为空了
            if (Config.isNight) {
                getDelegate().setLocalNightMode(
                        AppCompatDelegate.MODE_NIGHT_YES);
            } else {
                getDelegate().setLocalNightMode(
                        AppCompatDelegate.MODE_NIGHT_NO);
            }
            // 调用 recreate() 使设置生效
            recreate();
        }
        //setContentView必须放在最后
        setContentView(R.layout.activity_main);
    }
    public void click(View view){
            Config.isNight = !Config.isNight;
        finish();
        startActivity(new Intent(this,MainActivity.class));
    }

需要注意的是,当前的主题要继承Theme.AppCompat.DayNight

Bottom Sheets

Android Bottom Sheet详解

ActionBarDrawerToggle

这里写图片描述

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        drawerLayout1 = (DrawerLayout) findViewById(R.id.draw_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawerLayout1, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawerLayout1.addDrawerListener(toggle);
        toggle.syncState();

设置fragment切换时的方向(只有5.0以上才有效果)
这里写图片描述

  Slide slideTransition;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//5.0以上才有效果
            //Gravity.START部分机型崩溃java.lang.IllegalArgumentException: Invalid slide direction
            slideTransition = new Slide(Gravity.BOTTOM);
            slideTransition.setDuration(4000);
            fragment.setEnterTransition(slideTransition);
            fragment.setExitTransition(slideTransition);
        }

Butterknife在fragment中使用是要在onDestroyView中去除绑定

  mUnbinder = ButterKnife.bind(this, view);
  .......

  @Override
    public void onDestroyView() {
        super.onDestroyView();
        //Fragment中要去除绑定
        mUnbinder.unbind();
        mWeixinPresenter.unsubcrible();
    }

PopupMenu
这里写图片描述

 PopupMenu popupMenu = new PopupMenu(mContext, holder.btnWeixin);
                popupMenu.getMenuInflater().inflate(R.menu.pop_menu, popupMenu.getMenu());

  popupMenu.show();

给RecyclerView条目添加进入动画
这里写图片描述
在adapter的onBindViewHolder方法里面调用如下方法

//传入当前条目和位置
 runEnterAnimation(holder.itemView, position);

  private void runEnterAnimation(View view, int position) {
        view.setTranslationY(ScreenUtil.getScreenHight(mContext));
        view.animate()
                .translationY(0)
                .setStartDelay(100 * (position % 5))
                .setInterpolator(new DecelerateInterpolator(3.f))
                .setDuration(4000)
                .start();
    }

如何判断RecyclerView滑动到底部了,该加载第二页数据了

 swipeTarget.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if (dy > 0) //向上滑动到底部
                {
                    visibleItemCount = mLinearLayoutManager.getChildCount();
                    totalItemCount = mLinearLayoutManager.getItemCount();
                    pastVisiblesItems = mLinearLayoutManager.findFirstVisibleItemPosition();
            //当前可见条目数加上之前的条目总数=总得条目数,该加载第二页了
                    if (!loading && (visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                        loading = true;
                        onLoadMore();
                    }
                }
            }
        });

设置页面进入和退出缩放动画
这里写图片描述
在进入的activity的oncreate方法中overridePendingTransition(R.anim.zoomin, R.anim.zoomout);
下面是动画的布局文件

//zoomin
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator" >
    <scale
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromXScale="2.0"
        android:fromYScale="2.0"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale="1.0"
        android:toYScale="1.0" />
</set>
//zoomout
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:zAdjustment="top">
    <scale
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale=".5"
        android:toYScale=".5" />
    <alpha
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromAlpha="1.0"
        android:toAlpha="0" />
</set>

webview缓存功能
WebView中存在着两种缓存:网页数据缓存(存储打开过的页面及资源)、H5缓存(即AppCache)。

   if (!NetWorkUtil.isNetWorkAvailable(this))
            //加载缓存,不管是否过期
            webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        else
        //加载缓存,过期就不加载
            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        webSettings.setJavaScriptEnabled(true);
        webSettings.setUseWideViewPort(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setBuiltInZoomControls(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        webSettings.setAppCachePath(getCacheDir().getAbsolutePath() + "/webViewCache");
        webSettings.setAllowFileAccess(true);
        //开启AppCache
        webSettings.setAppCacheEnabled(true);
        webSettings.setLoadWithOverviewMode(true);

注意:上面有句代码我们手动设置了webview的缓存目录,但是实际当我们去文件目录下发现这个目录并不存在,可能是因为webview自己会创建缓存目录吧,这个目录名称为app_webview
这里写图片描述

当条目中出现复用导致错误显示的时候,可以使用SparseBooleanArray解决

  if (mSparseBooleanArray.get(Integer.parseInt(itHomeItem.getNewsid()))){
            holder.btnDetail.setBackgroundResource(R.drawable.ic_expand_less_black_24px);
            holder.tvDescription.setVisibility(View.VISIBLE);
        }else{
            holder.btnDetail.setBackgroundResource(R.drawable.ic_expand_more_black_24px);
            holder.tvDescription.setVisibility(View.GONE);
        }
        holder.btnDetail.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (holder.tvDescription.getVisibility() == View.GONE) {
                    holder.btnDetail.setBackgroundResource(R.drawable.ic_expand_less_black_24px);
                    holder.tvDescription.setVisibility(View.VISIBLE);
                    mSparseBooleanArray.put(Integer.parseInt(itHomeItem.getNewsid()),true);
                } else {
                    holder.btnDetail.setBackgroundResource(R.drawable.ic_expand_more_black_24px);
                    holder.tvDescription.setVisibility(View.GONE);
                    mSparseBooleanArray.put(Integer.parseInt(itHomeItem.getNewsid()),false);
                }
            }
        });

删除缓存目录cache下的文件及文件夹

   public static boolean deleteDir(File dir) {
        if (dir != null && dir.isDirectory()) {
            String[] children = dir.list();
            for (String aChildren : children) {
                boolean success = deleteDir(new File(dir, aChildren));
                if (!success) {
                    return false;
                }
            }
        }
        assert dir != null;
        return dir.delete();
    }

获取缓存路径下文件大小

  public static long getFolderSize(File file) {
        long size = 0;
        try {
            File[] fileList = file.listFiles();
            for (File aFileList : fileList) {
                // 如果下面还有文件
                if (aFileList.isDirectory()) {
                    size = size + getFolderSize(aFileList);
                } else {
                    size = size + aFileList.length();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return size;
    }

缓存显示处理

  public static String getCacheSize(File file) {
        return getFormatSize(getFolderSize(file));
    }
  public static String getFormatSize(double size) {
        double kiloByte = size / 1024;
        if (kiloByte < 1) {
//            return size + "Byte";
            return "0K";
        }

        double megaByte = kiloByte / 1024;
        if (megaByte < 1) {
            BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
            return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "KB";
        }

        double gigaByte = megaByte / 1024;
        if (gigaByte < 1) {
            BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
            return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "MB";
        }

        double teraBytes = gigaByte / 1024;
        if (teraBytes < 1) {
            BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
            return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "GB";
        }
        BigDecimal result4 = new BigDecimal(teraBytes);
        return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
                + "TB";
    }

BigDecimal使用

BigDecimal.setScale用于格式化小数点 
setScale(1)表示保留以为小数,默认用四舍五入方式 
setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3 
setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4 
setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下舍

OkHttp结合Retrofit实现缓存

我们拿一个请求来看

public class GuokrRequest {

    private GuokrRequest() {}

    private static final String CACHE_CONTROL = "Cache-Control";//请求头,指定请求和响应遵循的缓存机制
    private static final Object monitor = new Object();//锁对象
    private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            if (NetWorkUtil.isNetWorkAvailable(MicroApplication.getContext())) {
                int maxAge = 60; // 在线缓存在1分钟内可读取
                return originalResponse.newBuilder()
                        //Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。
                        // 在HTTP/1.1协议中,它的含义和Cache- Control:no-cache相同。
                        .removeHeader("Pragma")
                        .removeHeader(CACHE_CONTROL)//remove掉其他的缓存头,避免服务端进行一些限制,导致客户端不能进行缓存
                        //public:告知任何途径的缓存者,可以无条件的缓存该响应.
                        .header(CACHE_CONTROL, "public, max-age=" + maxAge)
                        .build();
            } else {
                int maxStale = 60 * 60 * 24 * 28; // 离线时缓存保存4周
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader(CACHE_CONTROL)
                        //only-if-cached:告知缓存者,我希望内容来自缓存,我并不关心被缓存响应,是否是新鲜的
                        //max-stale:意思是,我允许缓存者,发送一个,过期不超过指定秒数的,陈旧的缓存.
                        .header(CACHE_CONTROL, "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
        }
    };

    private static File httpCacheDirectory = new File(MicroApplication.getContext().getCacheDir(), "guokrCache");
    private static int cacheSize = 10 * 1024 * 1024; // 10 MiB
    private static Cache cache = new Cache(httpCacheDirectory, cacheSize);

    private static OkHttpClient client = new OkHttpClient.Builder()
            .addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)//保证1分钟内走缓存
            .addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)//超过1分钟,读取离线缓存
            .cache(cache)
            .build();

    private static GuokrApi guokrApi = null;

    public static GuokrApi getGuokrApi() {
        synchronized (monitor) {
            if (guokrApi == null) {
                guokrApi = new Retrofit.Builder()
                        .baseUrl("http://www.guokr.com")
                        .client(client)
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .addConverterFactory(GsonConverterFactory.create())
                        .build().create(GuokrApi.class);
            }
            return guokrApi;
        }
    }
}

然后给出一个学习的链接
Retrofit2.0使用总结及注意事项

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值