前言
前几天学习了布局相关的内容,今天继续学习ListView、RecyclerView、动画。如有错误,欢迎指正,互相学习下。
1.ListView
联想到iOS里的UITableView,单纯创建一个listView不够,还需要item(iOS里面的Cell)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ListView
android:id="@+id/lv_one"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:text="item_text"
android:id="@+id/item_tv"
android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout>
然后需要一个模型类,Java里面叫做Bean类吧。
package com.kowaii.mylistview;
public class Bean {
private String name;
public Bean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
listView获取数据需要通过adapter,自己实现一个类继承BaseAdapter。
package com.kowaii.mylistview;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<Bean> dataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.lv_one);
for (int i = 0; i < 20; i++) {
Bean bean = new Bean("第" + i + "个人");
dataList.add(bean);
}
MyAdapter adapter = new MyAdapter(dataList,this);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.e("hako","onItemClick position = " + position);
}
});
}
}
package com.kowaii.mylistview;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
public class MyAdapter extends BaseAdapter {
private List<Bean> data;
private Context context;
public MyAdapter(List<Bean> data, Context context) {
this.data = data;
this.context = context;
}
@Override
public int getCount() {
return this.data.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//此处跟iOS的tableview和collectionView比较类似,有复用机制,因此需要判断是否存在创建过的。
if(convertView == null){
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.listview_item,parent,false);
//直接findViewById比较耗时,应减少此操作的次数.
viewHolder.textView = convertView.findViewById(R.id.item_tv);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(data.get(position).getName());
Log.e("hako","getView position = " + position);
return convertView;
}
//此处使用ViewHolder是为了优化性能
private final class ViewHolder{
TextView textView;
}
}
最终实现效果
2.RecyclerView
使用起来跟ListView第一个不同,就是需要用gradle去导包。 需要去build.gradle里面dependencies添加依赖,然后点击同步就可以了。
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
}
第二个不同点,RecyclerView需要使用继承RecyclerView.Adapter的子类来做Adpater,因此内部实现的方法也大为不同。
package com.kowaii.myrecyclerview;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder>{
private List<Bean> data;
private Context context;
public MyAdapter(List<Bean> data, Context context) {
this.data = data;
this.context = context;
}
@NonNull
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = View.inflate(context,R.layout.recyclerview_item,null);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyAdapter.MyViewHolder holder, int position) {
holder.textView.setText(data.get(position).getName());
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.item_rv);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mOnItemClickListener != null){
mOnItemClickListener.onRecyclerViewItemClick(getAbsoluteAdapterPosition());
}
}
});
}
}
//RecyclerView没有像ListView提供onItemClickListener,需要自行实现.
private OnRecyclerViewOnClickListener mOnItemClickListener;
public void setOnItemClickListener(OnRecyclerViewOnClickListener listener){
this.mOnItemClickListener = listener;
}
public interface OnRecyclerViewOnClickListener{
void onRecyclerViewItemClick(int position);
}
}
public class MainActivity extends AppCompatActivity {
private List<Bean> dataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.rv_one);
// LinearLayoutManager manager = new LinearLayoutManager(this);
// recyclerView.setLayoutManager(manager);
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
for (int i = 0; i < 200; i++) {
Bean bean = new Bean("第" + i + "个人");
dataList.add(bean);
}
MyAdapter adapter = new MyAdapter(dataList,this);
adapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewOnClickListener() {
@Override
public void onRecyclerViewItemClick(int position) {
Log.e("hako","RecyclerView onclick position = " + position);
}
});
//只设置Adapter是无法正常显示,需要设置LayoutManager。
recyclerView.setAdapter(adapter);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_one"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:text="item_text"
android:id="@+id/item_rv"
android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout>
后续进阶还有自定义LayoutManager以及优化等等,先MARK了 Android性能优化必知:RecyclerView性能优化分析.
2.1动画-逐帧动画
需要创建一个drawable resource,把每一帧的图片设置进去,duration单位是毫秒.
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dragon00" android:duration="100" />
<item android:drawable="@drawable/dragon01" android:duration="100" />
<item android:drawable="@drawable/dragon02" android:duration="100" />
<item android:drawable="@drawable/dragon03" android:duration="100" />
</animation-list>
把background设置后并不会直接播放动画,需要通过代码启动
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@drawable/frame_animation"
>
</RelativeLayout>
package com.kowaii.myframeanimation;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private AnimationDrawable anidraw;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout = findViewById(R.id.relative_layout);
anidraw = (AnimationDrawable) layout.getBackground();
layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(anidraw.isRunning()){
anidraw.stop();
}else {
anidraw.start();
}
}
});
}
}
2.2动画-补间动画
res下面创建一个目录,添加对应的动画效果
pivotX代表X的中轴,pivotY代表Y的中轴点 作用是旋转的时候以什么为中心。缩放的时候以什么为中心
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageview_anim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="300dp"
android:maxWidth="300dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/chachuqu"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
附赠一张图片
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="2000"
></alpha>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="2000">
</rotate>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:fromXScale="1"
android:toXScale="2.5"
android:fromYScale="1"
android:toYScale="2.5"
android:pivotY="50%"
android:pivotX="50%"
android:duration="2000">
</scale>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="400"
android:fromYDelta="0"
android:toYDelta="400"
android:duration="2000"></translate>
</set>
package com.kowaii.mytweenedanimation;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.imageview_anim);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.alpha);
// Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.rotate);
Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.translate);
// Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.scale);
imageView.startAnimation(animation);
}
});
}
}
2.3动画-属性动画
部分引用来自
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,1000);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
Log.e("hako","value = " + value);
}
});
valueAnimator.start();
ValueAnimator是Property Animation系统的核心类,它包含了配置Property Animation属性的大部分方法,那要实现一个Property Animation,都需要直接或间接使用ValueAnimator类。那接下来我们将借助ValueAnimator类提供的方法来实现各种各样的动画效果,不过在此之前我们来学习一下实现动画的几个关键知识点。使用ValueAnimator实现动画的步骤及实践
那一般使用ValueAnimator实现动画分为以下七个步骤:
- 调用ValueAnimation类中的ofInt(int...values)、ofFloat(String propertyName,float...values)等静态方法实例化ValueAnimator对象,并设置目标属性的属性名、初始值或结束值等值;
- 调用addUpdateListener(AnimatorUpdateListener mListener)方法为ValueAnimator对象设置属性变化的监听器;
- 创建自定义的Interpolator(插值器),调用setInterpolator(TimeInterpolator value)为ValueAniamtor设置自定义的Interpolator;(可选,不设置默认为缺省值)
- 创建自定义的TypeEvaluator(估值器),调用setEvaluator(TypeEvaluator value)为ValueAnimator设置自定义的TypeEvaluator;(可选,不设置默认为缺省值)
- 在AnimatorUpdateListener 中的实现方法为目标对象的属性设置计算好的属性值。
- 设置动画的持续时间、是否重复及重复次数等属性;
- 为ValueAnimator设置目标对象并开始执行动画。
下面是ValueAnimator的子类 ObjectAnimator
TextView textView = findViewById(R.id.textview_one);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"alpha",0f,1f);
objectAnimator.setDuration(2000);
objectAnimator.start();
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationPause(Animator animation) {
super.onAnimationPause(animation);
}
@Override
public void onAnimationResume(Animator animation) {
super.onAnimationResume(animation);
}
});
使用ObjectAnimator实现动画也有一些要求和限制,一般有以下几点需要注意:
- 动画显示的属性必须带有一个 setter 方法(以骆驼拼写法命名),格式类似 set()。 因为 ObjectAnimator 会在动画期间自动更新属性值,它必须能够用此 setter 方法访问到该属性。 例如:假设属性名称为foo,则需要有一个setFoo()方法。 而你如果此 setter 方法不存在,那么我们可以有以下三种选择:
- 如果权限允许的话,直接在类中增加此 setter 方法;
- 修改封装类来增加此 setter 方法,并让该封装类来接收属性值并传给初始的对象;
- 换用 ValueAnimator。
- 如果在调用 ObjectAnimator 的某个工厂方法时,我们只为 values... 参数指定了一个值,那此值将被认定为动画属性的结束值。 这样的话,动画显示的属性必须带有一个 getter 方法,用于获取动画的起始值。 此 getter 方法必须以get()的格式命名。 例如:假设属性名为foo,则需要有一个getFoo()方法。
- 动画属性的 getter 方法(如果必要的话)和 setter 方法所操作数据的类型必须与 ObjectAnimator 中设定的起始和结束值相同。