Android JetPack学习总结(十四)

实现一个简易的图片画廊

Demo地址

数据类:

public class Pixabay {
    private int totalHits;
    private int total;
    private ArrayList<PhotoItem> hits=new ArrayList<>();

    public int getTotalHits() {
        return totalHits;
    }

    public void setTotalHits(int totalHits) {
        this.totalHits = totalHits;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public ArrayList<PhotoItem> getHits() {
        return hits;
    }

    public void setHits(ArrayList<PhotoItem> hits) {
        this.hits = hits;
    }
}

class PhotoItem{
    @SerializedName("webformatURL")
    private String preViewURL;
    @SerializedName("largeImageURL")
    private String fullURL;
    @SerializedName("id")
    private int photoId;

    public String getPreViewURL() {
        return preViewURL;
    }

    public void setPreViewURL(String preViewURL) {
        this.preViewURL = preViewURL;
    }

    public String getFullURL() {
        return fullURL;
    }

    public void setFullURL(String fullURL) {
        this.fullURL = fullURL;
    }

    public int getPhotoId() {
        return photoId;
    }

    public void setPhotoId(int photoId) {
        this.photoId = photoId;
    }
}

用单例的方式获取Volley:

public class VolleySingleton {
    private static VolleySingleton volleySingleton=null;
    private static RequestQueue requestQueue=null;
    private Context mContext;

    public static synchronized VolleySingleton getInstance(Context context){
        if (volleySingleton==null){
            volleySingleton=new VolleySingleton(context);
        }
        return volleySingleton;
    }
    private VolleySingleton(Context context){
        mContext=context;
        requestQueue= getRequestQueue();
    }
    public RequestQueue getRequestQueue(){
        if (requestQueue==null){
            requestQueue= Volley.newRequestQueue(mContext);
        }
        return requestQueue;
    }
}

构造一个ViewModel以存放获取到的图片的信息:

public class GalleryViewModel extends AndroidViewModel {
    private MutableLiveData<List<PhotoItem>> photoListLive= new MutableLiveData<>();
    private ArrayList<String> keyWords=new ArrayList<>();
    public GalleryViewModel(@NonNull Application application) {
        super(application);
        keyWords.add("cat");
        keyWords.add("dog");
        keyWords.add("car");
        keyWords.add("beauty");
        keyWords.add("phone");
        keyWords.add("computer");
        keyWords.add("flower");
        keyWords.add("animal");
    }

    public void fetchData(){
        StringRequest stringRequest=new StringRequest
                (Request.Method.GET,
                        getUrl(),
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                Gson gson=new Gson();
                                Pixabay pixabay=gson.fromJson(response,Pixabay.class);
                                photoListLive.setValue(pixabay.getHits());
                                Log.i("GalleryViewModel",pixabay.getHits().toString());
                            }
                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.i("GalleryViewModel",error.toString());
                            }
                        });
        VolleySingleton.getInstance(getApplication()).getRequestQueue().add(stringRequest);
    }

    private String getUrl(){
        return "https://pixabay.com/api/?key=18976664-784daca1700270b238f252820&q="+keyWords.get(new Random().nextInt(keyWords.size()));
    }

    public MutableLiveData<List<PhotoItem>> getPhotoListLive() {
        return photoListLive;
    }
}

写一个RecyclerView的adapter

public class GalleryAdapter extends ListAdapter<PhotoItem, MyViewHolder> {
    protected GalleryAdapter() {
        super(new DiffUtil.ItemCallback<PhotoItem>() {
            @Override
            public boolean areItemsTheSame(@NonNull PhotoItem oldItem, @NonNull PhotoItem newItem) {
                return oldItem==newItem;
            }

            @Override
            public boolean areContentsTheSame(@NonNull PhotoItem oldItem, @NonNull PhotoItem newItem) {
                return oldItem.getPhotoId()==newItem.getPhotoId();
            }
        });
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.gallery_cell,parent,false);
        MyViewHolder holder=new MyViewHolder(view);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
        ImageView imageView=holder.itemView.findViewById(R.id.imageView);
        final ShimmerLayout shimmerLayout=holder.itemView.findViewById(R.id.shimmerLayoutCell);
        shimmerLayout.setShimmerColor(0x55ffffff);//55表示透明度
        shimmerLayout.setShimmerAngle(0);
        shimmerLayout.startShimmerAnimation();
        Glide.with(holder.itemView)//view
            .load(getItem(position).getPreViewURL())
                .placeholder(R.drawable.ic_baseline_photo_24)
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        return false;
                    }

                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        shimmerLayout.stopShimmerAnimation();
                        return false;
                    }
                })
                .into(imageView);
    }
}

class MyViewHolder extends RecyclerView.ViewHolder{

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
    }
}

在GalleryFragment中显示界面:

public class GalleryFragment extends Fragment {

    GalleryViewModel galleryViewModel;

    GalleryAdapter galleryAdapter;
    RecyclerView recyclerView;
    public GalleryFragment() {
        // Required empty public constructor
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        galleryViewModel=new ViewModelProvider(this).get(GalleryViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view=inflater.inflate(R.layout.fragment_gallery, container, false);
        recyclerView=view.findViewById(R.id.recyclerView);
        galleryAdapter=new GalleryAdapter();
        recyclerView.setAdapter(galleryAdapter);
        recyclerView.setLayoutManager(new GridLayoutManager(requireContext(),2));

        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        galleryViewModel.getPhotoListLive().observe(getViewLifecycleOwner(), new Observer<List<PhotoItem>>() {
            @Override
            public void onChanged(List<PhotoItem> photoItems) {
                galleryAdapter.submitList(photoItems);
            }
        });
        if (galleryViewModel.getPhotoListLive().getValue()==null){//一定要放在观察者下面
            galleryViewModel.fetchData();
        }
    }
        galleryViewModel.getPhotoListLive().observe(getViewLifecycleOwner(), new Observer<List<PhotoItem>>() {
            @Override
            public void onChanged(List<PhotoItem> photoItems) {
                galleryAdapter.submitList(photoItems);
            }
        });

    }
}

有一个我非常疑惑的点:如果我在viewModel中一开始就初始化了photoList(即不判断是否为空,而是判断size是否为0),当且仅当第一次打开app时会加载图片。

至此为止,已经可以初步的完成了最基础的功能,即打开app之后就能加载图片:

实现下拉刷新,并在刷新后取消动画:

@Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
       galleryViewModel.getPhotoListLive().observe(getViewLifecycleOwner(), new Observer<List<PhotoItem>>() {
            @Override
            public void onChanged(List<PhotoItem> photoItems) {
                galleryAdapter.submitList(photoItems);
                swipeRefreshLayout.setRefreshing(false);
//取消动画
            }
        });

        //...

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                galleryViewModel.fetchData();
            }
        });
    }

添加一个菜单栏来实现刷新功能,同样添加刷新动画,并且延长刷新动画时间:

//GalleryFragment
@Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu,menu);
    }

     @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.swipeIndicator:
                swipeRefreshLayout.setRefreshing(true);
//添加刷新动画
                galleryViewModel.fetchData();
        }
        return super.onOptionsItemSelected(item);
    }

@Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setHasOptionsMenu(true);
galleryViewModel.getPhotoListLive().observe(getViewLifecycleOwner(), new Observer<List<PhotoItem>>() {
            @Override
            public void onChanged(List<PhotoItem> photoItems) {
                galleryAdapter.submitList(photoItems);
                Handler handler=new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        swipeRefreshLayout.setRefreshing(false);
                    }
                },1000);//使得加载完成后将取消动画的动作推迟1s,即这个动画能多做1s

            }
        });
//...
}

到此,这个加载、刷新图片的页面就可以完成了。接下来将会去写第二个页面,即查看大图。

首先要先将Adapter中留白的点击监听器补全,并将这个photoItem传入:

//GalleryAdapter
@NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.gallery_cell,parent,false);
        final MyViewHolder holder=new MyViewHolder(view);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Bundle bundle=new Bundle();
                bundle.putParcelable("PHOTO", getItem(holder.getAdapterPosition()));
                NavController navController = Navigation.findNavController(holder.itemView);
                navController.navigate(R.id.action_galleryFragment_to_photoFragment, bundle);

            }
        });
        return holder;
    }

其中需要先将PhotoItem这个类序列化:

class PhotoItem implements Parcelable{
}

在PhotoFragment中接受PhotoItem,并加载图片:

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view=inflater.inflate(R.layout.fragment_photo, container, false);
        shimmerLayout=view.findViewById(R.id.shimmerLayoutPhoto);
        shimmerLayout.setShimmerColor(0x55ffffff);//55表示透明度
        shimmerLayout.setShimmerAngle(0);
        shimmerLayout.startShimmerAnimation();
        photoView =view.findViewById(R.id.photoView);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        PhotoItem photoItem= getArguments() != null ? (PhotoItem) getArguments().getParcelable("PHOTO") : null;

        Glide.with(requireContext())
                .load(photoItem != null ? photoItem.getFullURL() : null)
                .placeholder(R.drawable.ic_baseline_photo_24)
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        return false;
                    }

                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        shimmerLayout.stopShimmerAnimation();
                        return false;
                    }
                })
                .into(photoView);
    }

设置返回键:

//MainActivity
public class MainActivity extends AppCompatActivity {
    private NavController navController;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        navController = Navigation.findNavController(this,R.id.fragment);
        NavigationUI.setupActionBarWithNavController(this,navController);
    }

    @Override
    public boolean onSupportNavigateUp() {
        return super.onSupportNavigateUp()||navController.navigateUp();
    }
}

最终效果:

发表评论