MEIZHI gankio 程序 一

来源:互联网 发布:operamasks ui.min.js 编辑:程序博客网 时间:2024/06/05 08:52

项目地址:https://github.com/drakeet/Meizhi

整个项目的封装思想很值得学习。之前写过的一篇文章

一、
再倒入包的时候 看到他的build.gradle  许多奇怪的配置 
原来是使用了config.gradle统一管理项目,先新建一个config.gradle的文件

ext {
    android = [compileSdkVersion: 23,
               buildToolsVersion: "23.0.3",
               applicationId    : "me.drakeet.meizhi",
               minSdkVersion    : 16,
               targetSdkVersion : 22,
               versionCode      : 360,
               versionName      : "2.6.0"]
    dependencies = ["appcompat-v7"        : "com.android.support:appcompat-v7:23.4.0",
                    "design"              : "com.android.support:design:23.4.0",
                    "recyclerview-v7"     : "com.android.support:recyclerview-v7:23.4.0",
                    "nineoldandroids"     : "com.nineoldandroids:library:2.4.0",
                    "picasso"             : "com.squareup.picasso:picasso:2.5.2",
                    "photoview"           : "com.github.chrisbanes.photoview:library:1.2.3",
                    "numberprogressbar"   : "com.daimajia.numberprogressbar:library:1.2@aar",
                    "umeng-analytics"     : "com.umeng.analytics:analytics:latest.integration",
                    "retrofit"            : "com.squareup.retrofit:retrofit:1.9.0",
                    "rxandroid"           : "io.reactivex:rxandroid:1.0.0",
                    "okhttp-urlconnection": "com.squareup.okhttp:okhttp-urlconnection:2.0.0",
                    "okhttp"              : "com.squareup.okhttp:okhttp:2.0.0",
                    "butterknife"         : "com.jakewharton:butterknife:7.0.1",
                    "otto"                : "com.squareup:otto:1.3.8",
                    "glide"               : "com.github.bumptech.glide:glide:3.7.0"]
}
然后在项目的build.gradle文件中引入
apply from: "config.gradle"
这样使用config中的配置就可以管理所有module中的配置了.
如何使用
在module的build.gradle中使用:



compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
    applicationId rootProject.ext.android.applicationId
    minSdkVersion rootProject.ext.android.minSdkVersion
    targetSdkVersion rootProject.ext.android.targetSdkVersion
    versionCode rootProject.ext.android.versionCode
    versionName rootProject.ext.android.versionName
}
compile rootProject.ext.dependencies["design"]
compile rootProject.ext.dependencies["appcompat-v7"]
compile rootProject.ext.dependencies["recyclerview-v7"]
compile rootProject.ext.dependencies["picasso"]


二、
从Mainactivity来看,其获取layout和findviewbyid使用到了Butterknife库,然后学习了使用最新版本8.0.4.
减少代码量,8.0不同于其他,需要一些不同的配置才能生效,否则报获取view空



如何改变:
Project的build.gradle文件中:


buildscript {
repositories {
  jcenter()
}
dependencies {
  classpath 'com.android.tools.build:gradle:2.0.0'
  classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //8.X以上需要多配置的代码
}
allprojects {
repositories {
  jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}


app的build.gradle文件中内容:
apply plugin: 'com.neenbedankt.android-apt'//8.X以上需要多配置的代码


dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.jakewharton:butterknife:8.1.0'
apt 'com.jakewharton:butterknife-compiler:8.1.0'//8.X以上需要多配置的代码
}




代码中的使用,具体就是之前的inject变成了现在的bind,其他变化不大:
(与最早之前的相比。bind变为了bindview 并且有了bindviews。)

public class MainActivity extends AppCompatActivity {


  @BindView(R.id.btn_qq) Button qqBtn;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);


      ButterKnife.bind(this);
  }
  @OnClick(R.id.btn_qq) void qqBtnClick(){
      Toast.makeText(MainActivity.this, "123", Toast.LENGTH_SHORT).show();
  }
}




这些在官网文档也是有的 (https://github.com/JakeWharton/butterknife);




三、
然后他的主界面是一个Recycleview加载从gank.io接口中传来的图片和内容,他建立了一个bean实体类,也是用到了创建表格,存在了表格
里边,创建表用到的不是自带的sqlite数据库,用到的是liteorm第三方库,性能比自带的好一点。
android-lite-orm库的github地址:

https://github.com/litesuits/android-lite-orm
新建表:
import com.litesuits.orm.db.annotation.Column;
import com.litesuits.orm.db.annotation.Table;
import java.util.Date;


/**
 * Created by drakeet on 6/20/15.
 */
@Table("meizhis") public class Meizhi extends Soul {


    @Column("url") public String url;
    @Column("type") public String type;
    @Column("desc") public String desc;
    @Column("who") public String who;
    @Column("used") public boolean used;
    @Column("createdAt") public Date createdAt;
    @Column("updatedAt") public Date updatedAt;
    @Column("publishedAt") public Date publishedAt;
    @Column("imageWidth") public int imageWidth;
    @Column("imageHeight") public int imageHeight;
}


@Table  @Column 为标签.看源码就会很清楚. 就是用的建表语句建表语句:CREATE TABLE IF NOT EXISTS test_model (id INTEGER PRIMARY KEY AUTOINCREMENT ,name TEXT, login TEXT DEFAULT true) 




四、
然后是调用接口 gason结束数据到实体中,然后图片的加载是用到的picasso
在加载图片这块写的很好,可以学习如何activity带动画的跳转到另一个activity,并且用picasso预加载将图片加载到Imageview中,然后长按
图片弹出对话框,可以保存到指定目录,保存的代码块可以学习,








实现代码
mMeizhiListAdapter.setOnMeizhiTouchListener(getOnMeizhiTouchListener()); //添加点击事件
//区分点击妹子图片和点击itemView
private OnMeizhiTouchListener getOnMeizhiTouchListener() {
    return (v, meizhiView, card, meizhi) -> {
        if (meizhi == null) return;
        if (v == meizhiView && !mMeizhiBeTouched) { //使用标志位mMeizhiBeTouched过滤重复点击
            mMeizhiBeTouched = true;
        //利用Picasso预加载图片
            Picasso.with(this).load(meizhi.url).fetch(new Callback() {


                @Override public void onSuccess() { //如果预加载成功就跳转展示图片
                    mMeizhiBeTouched = false;
                    startPictureActivity(meizhi, meizhiView);
                }




                @Override public void onError() {mMeizhiBeTouched = false;}
            });
        } else if (v == card) { //如果是item被点击
            startGankActivity(meizhi.publishedAt);
        }
    };
}
查看MeizhiListAdapter的相关代码
找到调用mOnMeizhiTouchListener的地方.


class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {


    @Bind(R.id.iv_meizhi) RatioImageView meizhiView; //显示妹子图片的View
    @Bind(R.id.tv_title) TextView titleView; 
    View card; //即Item
    Meizhi meizhi; //封装的妹子图片对象




    public ViewHolder(View itemView) {
        super(itemView);
        card = itemView;
        ButterKnife.bind(this, itemView);
        meizhiView.setOnClickListener(this); //妹子图片设置点击事件
        card.setOnClickListener(this); //itemView设置点击事件
        meizhiView.setOriginalSize(50, 50);
    }




    @Override public void onClick(View v) {
        mOnMeizhiTouchListener.onTouch(v, meizhiView, card, meizhi);
    }
}
这里让ViewHolder实现点击事件. 让itemView和妹子图片的点击共用一个监听器.


这样做的好处就是方便,简单. 
但是ViewHolder以组合方式持有了MeiZhi这个对象的引用,不晓得恰不恰当. 
另外,代码中使用了Picasso的预加载功能, 如果能成功加载就打开图片浏览页面,不能则放弃打开. 这样的体验也不错.


转场动画
开启图片浏览的Activity这里有个转场动画.使用的是ActivityOptionsCompat.makeSceneTransitionAnimation(), 
平滑的将一个控件平移的过渡到第二个activity.


private void startPictureActivity(Meizhi meizhi, View transitView) {
    Intent intent = PictureActivity.newIntent(MainActivity.this, meizhi.url, meizhi.desc); //这种在PictureActivity中写newIntent的方式并推荐
    ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(
            MainActivity.this, transitView, PictureActivity.TRANSIT_PIC); //转场动画,PictureActivity.TRANSIT_PIC这个name很重要
    try {
        ActivityCompat.startActivity(MainActivity.this, intent, optionsCompat.toBundle());
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
        startActivity(intent);
    }
}
在PictureAcitity的onCreate()中调用了



ViewCompat.setTransitionName(mImageView, TRANSIT_PIC);
要使用这个方法就必须给两个不同Activity的中的布局元素设定同样的一个android:transitionName,然后还需要一个标志来告诉Window执行动画,因为这个只是在5.x上有效. 


此部分内容来自:http://www.voidcn.com/blog/maxwell0401/article/p-6145720.html 








五、RecycleView的滑动,一瀑布流形式显示图片和内容描述,  监听图片滚动,加载下一页的处理。




绑定监听器
final StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2,
        StaggeredGridLayoutManager.VERTICAL); //瀑布流布局管理器
mRecyclerView.setLayoutManager(layoutManager);
mMeizhiListAdapter = new MeizhiListAdapter(this, mMeizhiList);
mRecyclerView.setAdapter(mMeizhiListAdapter);
 /**
         * 第一次登陆的话  show的内容Snackbar展示
         */
        new Once(this).show("tip_guide_6", () -> {
            Snackbar.make(mRecyclerView, getString(R.string.tip_guide), Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.i_know, v -> {//点击的按钮
                    })
                    .show();
        });
mRecyclerView.addOnScrollListener(getOnBottomListener(layoutManager)); //添加滚动监听
监听器的实现
//滚动到底部自动加载下一页
RecyclerView.OnScrollListener getOnBottomListener(StaggeredGridLayoutManager layoutManager) {
    return new RecyclerView.OnScrollListener() {
        @Override public void onScrolled(RecyclerView rv, int dx, int dy) {
        //是否到达底部, 因为是2列,所以参数是new int[2], 判断第二列显示的View的数量是否显示完全
            boolean isBottom =layoutManager.findLastCompletelyVisibleItemPositions(new int[2])[1] >=
                            mMeizhiListAdapter.getItemCount() - PRELOAD_SIZE;
            if (!mSwipeRefreshLayout.isRefreshing() && isBottom) {
                if (!mIsFirstTimeTouchBottom) {
                    mSwipeRefreshLayout.setRefreshing(true); //显示刷新
                    mPage += 1; //加载下一页
                    loadData();
                } else {
                    mIsFirstTimeTouchBottom = false;
                }
            }
        }
    };
}
API说明
StaggeredGridLayoutManager#findLastCompletelyVisibleItemPositions()的API说明:
/** * Returns the adapter position of the last completely visible view for each span. * <p> 返回每一列可见的最后一个view的位置的数组 * @param into An array to put the results into. If you don't provide any, LayoutManager will * create a new one. 用来存放结果的数组into[] * @return The adapter position of the last fully visible item in each span. If a span does not * have any items, {@link RecyclerView#NO_POSITION} is returned for that span. */
 public int[] findLastCompletelyVisibleItemPositions(int[] into) {
     if (into == null) {
         into = new int[mSpanCount]; //根据设置的spanCount来创建数组
     } else if (into.length < mSpanCount) {
         throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                 + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
     }
     for (int i = 0; i < mSpanCount; i++) {
         into[i] = mSpans[i].findLastCompletelyVisibleItemPosition(); //每个span都是找到最后一个位置
     }
     return into;
 }










六、点击图片 预加载成功后的长按事件弹出对话框,保存图片到本地文件夹,然后会更新图片到相册。


有Mainactivit跳转到PictureActivity.在此进行处理 跳转期间还有动画效果同事进行图片的加载


/**
     * 保存图片到指定路径
     */
    private void saveImageToGallery() {
        // @formatter:off
        Subscription s = RxMeizhi.saveImageAndGetPathObservable(this, mImageUrl, mImageTitle)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(uri -> {
                    File appDir = new File(Environment.getExternalStorageDirectory(), "Meizhi");
                    String msg = String.format(getString(R.string.picture_has_save_to),
                            appDir.getAbsolutePath());
                    Toasts.showShort(msg);
                }, error -> Toasts.showLong(error.getMessage() + "\n再试试..."));
        // @formatter:on
        addSubscription(s);
    }







/**
     * 长按图片点击事项
     */
    private void setupPhotoAttacher() {
        mPhotoViewAttacher = new PhotoViewAttacher(mImageView);
        mPhotoViewAttacher.setOnViewTapListener((view, v, v1) -> hideOrShowToolbar());
        // @formatter:off
        mPhotoViewAttacher.setOnLongClickListener(v -> {
            new AlertDialog.Builder(PictureActivity.this)
                    .setMessage(getString(R.string.ask_saving_picture))
                    .setNegativeButton(android.R.string.cancel,
                            (dialog, which) -> dialog.dismiss())
                    .setPositiveButton(android.R.string.ok,
                            (dialog, which) -> {
                                saveImageToGallery();
                                dialog.dismiss();
                            })
                    .show();
            // @formatter:on
            return true;
        });
    }



关键处理类RxMeizhi.java 包括避免重复插入,进行预加载是否成功的判断, 以及进行图库的更行
/**
 * 简单重构了下,并且修复了重复插入图片问题
 * Created by drakeet on 8/10/15.
 */
public class RxMeizhi {


    public static Observable<Uri> saveImageAndGetPathObservable(Context context, String url, String title) {
        return Observable.create(new Observable.OnSubscribe<Bitmap>() {
            @Override public void call(Subscriber<? super Bitmap> subscriber) {
                Bitmap bitmap = null;
                try {
                    bitmap = Picasso.with(context).load(url).get();
                } catch (IOException e) {
                    subscriber.onError(e);
                }
                if (bitmap == null) {
                    subscriber.onError(new Exception("无法下载到图片"));
                }
                subscriber.onNext(bitmap);
                subscriber.onCompleted();
            }
        }).flatMap(bitmap -> {
            File appDir = new File(Environment.getExternalStorageDirectory(), "Meizhi");
            if (!appDir.exists()) {
                appDir.mkdir();
            }
            String fileName = title.replace('/', '-') + ".jpg";
            File file = new File(appDir, fileName);
            try {
                FileOutputStream outputStream = new FileOutputStream(file);
                assert bitmap != null;
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }


            Uri uri = Uri.fromFile(file);
            // 通知图库更新
            Intent scannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
            context.sendBroadcast(scannerIntent);
            return Observable.just(uri);
        }).subscribeOn(Schedulers.io());
    }
}



0 0
原创粉丝点击