前言

MultiType 这个项目,至今 v3.x 稳定多时,考虑得非常多,但也做得非常克制。原则一直是 直观、灵活、可靠、简单纯粹(其中直观和灵活是非常看重的)。

这是 MultiType 框架作者给出的项目简述。

作为一个 RecyclerView 的 Adapter 框架,感觉这项目的设计非常的优雅,而且可以满足很多常用的需求,而且像作者所说,该项目非常克制,没有因为便利而加入一些会导致项目臃肿的功能,它只提供了数据的绑定,其他的功能我们只需要稍微加以封装就可以实现。

为什么要封装

如果还没用过这个库的先去看看作者的文档

我们先来看看框架的原始用法:

Step 1. 创建一个 class,它将是你的数据类型或 Java bean / model. 对这个类的内容没有任何限制。示例如下:

public class Category {@NonNull public final String text;public Category(@NonNull String text) {this.text = text;}
}
复制代码

Step 2. 创建一个 class 继承 ItemViewBinder.

ItemViewBinder 是个抽象类,其中 onCreateViewHolder 方法用于生产你的 item view holder, onBindViewHolder 用于绑定数据到 Views. 一般一个 ItemViewBinder 类在内存中只会有一个实例对象,MultiType 内部将复用这个 binder 对象来生产所有相关的 item views 和绑定数据。示例:

public class CategoryViewBinder extends ItemViewBinder<Category, CategoryViewBinder.ViewHolder> {@NonNull @Overrideprotected ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {View root = inflater.inflate(R.layout.item_category, parent, false);return new ViewHolder(root);}@Overrideprotected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Category category) {holder.category.setText(category.text);}static class ViewHolder extends RecyclerView.ViewHolder {@NonNull private final TextView category;ViewHolder(@NonNull View itemView) {super(itemView);this.category = (TextView) itemView.findViewById(R.id.category);}}
}
复制代码

Step 3. 在 Activity 中加入 RecyclerView 和 List 并注册你的类型,示例:

public class MainActivity extends AppCompatActivity {private MultiTypeAdapter adapter;/* Items 等同于 ArrayList<Object> */private Items items;@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);/* 注意:我们已经在 XML 布局中通过 app:layoutManager="LinearLayoutManager"* 给这个 RecyclerView 指定了 LayoutManager,因此此处无需再设置 */adapter = new MultiTypeAdapter();/* 注册类型和 View 的对应关系 */adapter.register(Category.class, new CategoryViewBinder());adapter.register(Song.class, new SongViewBinder());recyclerView.setAdapter(adapter);/* 模拟加载数据,也可以稍后再加载,然后使用* adapter.notifyDataSetChanged() 刷新列表 */items = new Items();for (int i = 0; i < 20; i++) {items.add(new Category("Songs"));items.add(new Song("drakeet", R.drawable.avatar_dakeet));items.add(new Song("许岑", R.drawable.avatar_cen));}adapter.setItems(items);adapter.notifyDataSetChanged();}
}
复制代码

我把作者文档中的事例搬了过来,可以看到,使用还是非常简易的,沿用了原生 ViewHolder 的用法,上手很快。

  • 但是这也是一个非常不便的问题,因为作者没有进一步的封装,所以我们还需要为每个 Binder 去配置一个 ViewHolder ,所以我们还是做了很多重复性的工作。
  • 并且在 Adapter 或 Binder 中没有为我们提供 Item 的点击反馈接口,这样就导致我们的点击万一依赖到 Activity 或者 Fragment 的一些变量的话,又需要我们去写一个 Callback 。

所以我们的封装就是为了解决上面的两个问题。

封装

问题

上面说到我们封装就是要解决上面提到的两个问题,让其更好用:

  1. 封装 ViewHolder
  2. 添加点击事件
  3. 添加 Sample Binder
  4. 添加Header、Footer

第三点是随便添加上去的,用于只有一个 TextView 的 Item。

方案

1. 封装ViewHolder

思路其实很简单,就是创建一个 BaseViewHolder 来代替我们之前需要频繁创建的 ViewHolder.

废话少说,看代码:

public class BaseViewHolder extends RecyclerView.ViewHolder {private View mView;private SparseArray<View> mViewMap = new SparseArray<>();   // 1public BaseViewHolder(View itemView) {super(itemView);mView = itemView;}//返回根Viewpublic View getView() {return mView;}/*** 根据View的id来返回view实例*/public <T extends View> T getView(@IdRes int ResId) {View view = mViewMap.get(ResId);if (view == null) {view = mView.findViewById(ResId);mViewMap.put(ResId, view);}return (T) view;}
}复制代码

整个类就一个方法 getView 的两个重载,没有参数的 那个返回我们 Item 的根 View ,有参数的那个可以根据控件的 Id 来返回相对应 View。

getView(@IdRes int ResId) 方法中,我们用 ResId 为键,View 为值的 SparseArray 来存储当前 ViewHolder 的各种View,然后首次加载(即mViewMap 没有对应的值)时就用 findViewById 方法来获取相对View并存起来,然后复用的时候就可以直接重 mViewMap 中获取相对于的值(View)来进行数据绑定。

接着,为了方便,我们可以添加一系列的方法在此类中,例如:

 public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {TextView view = getView(viewId);view.setText(strId);return this;}public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {ImageView view = getView(viewId);view.setImageResource(imageResId);return this;}复制代码

这样一来,我们就可以在 Binder 类的onBindViewHolder中进行更加简便的数据绑定,例如:

@Override
protected void onBindViewHolder(@NonNull BaseViewHolder holder, @NonNull T item) {holder.setText(R.id.name,“张三”);holder.setImageResource(R.id.avatar,R.mimap.icon_avatar);
}
复制代码

2. 封装 ItemBinder

为了解决我们上面问题中的第2点,我们需要封装一个 ItemBinder 来实现我们的功能。代码如下:

public abstract class LwItemBinder<T> extends ItemViewBinder<T, LwViewHolder> {private OnItemClickListener<T> mListener;private OnItemLongClickListener<T> mLongListener;private SparseArray<OnChildClickListener<T>> mChildListenerMap = new SparseArray<>();private SparseArray<OnChildLongClickListener<T>> mChildLongListenerMap = new SparseArray<>();protected abstract View getView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);protected abstract void onBind(@NonNull LwViewHolder holder, @NonNull T item);@NonNull@Overrideprotected final LwViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {return new LwViewHolder(getView(inflater, parent));}@Overrideprotected final void onBindViewHolder(@NonNull LwViewHolder holder, @NonNull T item) {bindRootViewListener(holder, item);bindChildViewListener(holder, item);onBind(holder, item);}/*** 绑定子View点击事件** @param holder* @param item*/private void bindChildViewListener(LwViewHolder holder, T item) {//点击事件for (int i = 0; i < mChildListenerMap.size(); i++) {int id = mChildListenerMap.keyAt(i);View view = holder.getView(id);if (view != null) {view.setOnClickListener(v -> {OnChildClickListener<T> l = mChildListenerMap.get(id);if (l!=null){l.onChildClick(holder,view,item);}});}}//长按点击for (int i = 0; i < mChildLongListenerMap.size(); i++) {int id = mChildLongListenerMap.keyAt(i);View view = holder.getView(id);if (view != null) {view.setOnClickListener(v -> {OnChildLongClickListener<T> l = mChildLongListenerMap.get(id);if (l != null) {l.onChildLongClick(holder,view, item);}});}}}/*** 绑定根view** @param holder* @param item*/private void bindRootViewListener(LwViewHolder holder, T item) {//根View点击事件holder.getView().setOnClickListener(v -> {if (mListener != null) {mListener.onItemClick(holder, item);}});//根View长按事件holder.getView().setOnLongClickListener(v -> {boolean result = false;if (mLongListener != null) {result = mLongListener.onItemLongClick(holder, item);}return result;});}/*** 点击事件*/public void setOnItemClickListener(OnItemClickListener<T> listener) {mListener = listener;}/*** 点击事件** @param id 控件id,可传入子view ID* @param listener*/public void setOnChildClickListener(@IdRes int id, OnChildClickListener<T> listener){mChildListenerMap.put(id,listener);}public void setOnChildLongClickListener(@IdRes int id, OnChildLongClickListener<T> listener){mChildLongListenerMap.put(id,listener);}/*** 长按点击事件*/public void setOnItemLongClickListener(OnItemLongClickListener<T> l) {mLongListener = l;}/*** 长按点击事件** @param id 控件id,可传入子view ID*/public void removeChildClickListener(@IdRes int id){mChildListenerMap.remove(id);}public void removeChildLongClickListener(@IdRes int id){mChildLongListenerMap.remove(id);}/*** 移除点击事件*/public void removeItemClickListener() {mListener = null;}public void removeItemLongClickListener() {mLongListener = null;}public interface OnItemLongClickListener<T> {boolean onItemLongClick(LwViewHolder holder, T item);}public interface OnItemClickListener<T> {void onItemClick(LwViewHolder holder, T item);}public interface OnChildClickListener<T> {void onChildClick(LwViewHolder holder, View child, T item);}public interface OnChildLongClickListener<T> {void onChildLongClick(LwViewHolder holder, View child, T item);}}复制代码

代码也很简单,提供了Click以及LongClick的监听,并且在 onCreateViewHolder()方法中将我们刚刚封装的 BaseViewHolder 给传进去,然后提供两个抽象方法:

  • getView(@NonNull LayoutInflater inflater,@NonNull ViewGroup parent)

    • 需要返回Item的View实例
  • onBind(@NonNull BaseViewHolder holder, @NonNull T item)
    • 在此方法内进行数据绑定

以后我们就不必为每个 Binder 都设置一套ViewHolder了,实例如下:

public class RankItemBinder extends LwItemBinder<Rank> {private final int[] RANK_IMG = {R.drawable.no_4,R.drawable.no_5,R.drawable.no_6,R.drawable.no_7,R.drawable.no_8,R.drawable.no_9,R.drawable.no_10};@Overrideprotected View getView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {return inflater.inflate(R.layout.item_rank, parent, false);}@Overrideprotected void onBind(@NonNull BaseViewHolder holder, @NonNull Rank item) {Context context = holder.getView().getContext();holder.setText(R.id.tv_name, item.getUserNickname());holder.setText(R.id.tv_num, context.getString(R.string.text_caught_doll_num, item.getCaughtNum()));loadCircleImage(context,item.getUserIconUrl(),0,0,holder.getView(R.id.iv_avatar));if (holder.getAdapterPosition() < 7) {holder.setImageResource(R.id.iv_rank, RANK_IMG[holder.getAdapterPosition()]);}}public void loadCircleImage(final Context context, String url, int placeholderRes, int errorRes, final ImageView imageView) {RequestOptions requestOptions = new RequestOptions().circleCrop();if (placeholderRes != 0) requestOptions.placeholder(placeholderRes);if (errorRes != 0) requestOptions.error(errorRes);Glide.with(context).load(url).apply(requestOptions).into(imageView);}
}复制代码

可以看到,非常的简洁,并且可以在 Activity 或 Fragment 中添加监听事件:

RankItemBinder binder = new RankItemBinder();
binder.setOnItemClickListener(new BaseItemBinder.OnItemClickListener<Rank>() {@Overridepublic void onItemClick(BaseViewHolder holder, Rank item) {ToastUtils.showShort("点击了"+item.getUserNickname());}
});复制代码

如果使用 lambda 表达式,则可以更简洁:

binder.setOnItemClickListener((holder, item) -> ToastUtils.showShort("点击了"+item.getUserNickname()));
复制代码

以上就是整套的封装了,很简单,但是也很实用,可以在日常开发中省下不少代码。

3. 封装Sample

上面说了,我们还可以通过继承这个 BaseItemBinder 来实现一个只有一个 TextView 的Sample:

public class SampleBinder extends LwItemBinder<Object> {public static final int DEFAULT_TEXT_SIZE = 15; //sppublic static final int DEFAULT_HEIGHT = 50;  //dppublic static final int DEFAULT_PADDING_HORIZONTAL = 6; //dppublic static final int DEFAULT_PADDING_VERTICAL = 4; //dp@Overrideprotected View getView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {Context context = parent.getContext();DisplayMetrics metrics = context.getResources().getDisplayMetrics();float density = metrics.density;int heightPx = dp2px(density, DEFAULT_HEIGHT);int paddingHorizontal = dp2px(density, DEFAULT_PADDING_HORIZONTAL);TextView textView = new TextView(context);textView.setTextSize(DEFAULT_TEXT_SIZE);textView.setGravity(Gravity.CENTER_VERTICAL);textView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0);ViewGroup.LayoutParams params =new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, heightPx);textView.setLayoutParams(params);custom(textView, parent);return textView;}@Overrideprotected void onBind(@NonNull LwViewHolder holder, @NonNull Object item) {TextView textView = holder.getView();textView.setText(item.toString());}private int dp2px(float density, float dp) {return (int) (density * dp + 0.5f);}protected void custom(TextView textView, ViewGroup parent) {}
}复制代码

很简单的一个扩展,根 View 就是一个 TextView,然后提供了一些属性的设置修改,如果不满足默认样式还可以重写 custom(TextView textView, ViewGroup parent)方法对 TextView 进行样式的修改,或者重写 custom(TextView textView, ViewGroup parent)方法在进行绑定的时候进行控件的属性修改等逻辑。

4. 添加Header、Footer

MultiType 其实本身就支持 HeaderViewFooterView,只要创建一个 Header.class - HeaderViewBinderFooter.class - FooterViewBinder 即可,然后把 new Header() 添加到 items 第一个位置,把 new Footer() 添加到 items 最后一个位置。需要注意的是,如果使用了 Footer View,在底部插入数据的时候,需要添加到 最后位置 - 1,即倒二个位置,或者把 Footer remove 掉,再添加数据,最后再插入一个新的 Footer.

这个是作者文档里面说的,简单,但是繁琐,既然我们要封装,肯定就不能容忍这么繁琐的事情。

先理一下要实现的点:

  • 一行代码添加 Header/Footer
  • 源数据的更改更新与 Header/Footer 无关

接下来看看具体实现:

public class LwAdapter extends MultiTypeAdapter {//...省略部分代码private HeaderExtension mHeader;private FooterExtension mFooter;/*** 添加Footer** @param o Header item*/public LwAdapter addHeader(Object o) {createHeader();mHeader.add(o);notifyItemRangeInserted(getHeaderSize() - 1, 1);return this;}/*** 添加Footer** @param o Footer item*/public LwAdapter addFooter(Object o) {createFooter();mFooter.add(o);notifyItemInserted(getItemCount() + getHeaderSize() + getFooterSize() - 1);return this;}/*** 增加Footer数据集** @param items Footer 的数据集*/public LwAdapter addFooter(Items items) {createFooter();mFooter.addAll(items);notifyItemRangeInserted(getFooterSize() - 1, items.size());return this;}private void createHeader() {if (mHeader == null) {mHeader = new HeaderExtension();}}private void createFooter() {if (mFooter == null) {mFooter = new FooterExtension();}}
}复制代码

先看上面的实现,用 addHeader(Object o)添加 Header,添加 Footer 同理,一行代码就实现,但是这个 addHeader(Object o) 方法里面的逻辑是怎样的呢,首先是调用了 createHeader(),即创建一个 HeaderExtension对象并把引用赋值给 mHeader,然后再调用mHeader.add(o)将我们传过来的 item 实例给添加进去,最后调用AdapternotifyItemInserted方法刷新一下列表就OK了。逻辑很简单,但是这样为什么就可以实现了添加 Header 的功能呢,HeaderExtension又是什么鬼呢?

接下来看看 HeaderExtension是什么?

public class HeaderExtension implements Extension {private Items mItems;public HeaderExtension(Items items) {this.mItems = items;}public HeaderExtension(){this.mItems = new Items();}@Overridepublic Object getItem(int position) {return mItems.get(position);}@Overridepublic boolean isInRange(int adapterSize, int adapterPos) {return adapterPos < getItemSize();}@Overridepublic int getItemSize() {return mItems.size();}@Overridepublic void add(Object o) {mItems.add(o);}@Overridepublic void remove(Object o) {mItems.add(o);}//...省略部分代码
}
复制代码

该类实现了Extension接口,我们调用add()方法就是将传过来的对象保存起来而已。整个类最主要的方法就是 isInRange(int adapterSize, int adapterPos) 方法,看到这个方法的实现相信你也能明白他的作用了,就是用来判断 Adapter里面传过来的 position 对应的 Item 是否是 Header.接下来看一下这个方法在 Adapter 内的使用在哪里:

#LwAdapter.java

 @Overridepublic final int getItemViewType(int position) {Object item = null;int headerSize = getHeaderSize();int mainSize = getItems().size();if (mHeader != null) {if (mHeader.isInRange(getItemCount(), position)) {item = mHeader.getItem(position);return indexInTypesOf(position, item);}}if (mFooter != null) {if (mFooter.isInRange(getItemCount(), position)) {int relativePos = position - headerSize - mainSize;item = mFooter.getItem(relativePos);return indexInTypesOf(relativePos, item);}}int relativePos = position - headerSize;return super.getItemViewType(relativePos);}
复制代码

第一次的调用在这里,到这里我们应该就恍然大悟了,原来就是根据 position 来判断是否用于 Header/Footer ,然后再用 父类里面的 indexInTypesOf(int,Object)来获取对应的类型。接着在 onCreateViewHolder(ViewGroup parent, int indexViewType)会自动创建我们对应的 ViewHolder,最后在onBindViewHolder()中再进行相应的绑定即可:

 @SuppressWarnings("unchecked")@Overridepublic final void onBindViewHolder(RecyclerView.ViewHolder holder, int position,@NonNull List<Object> payloads) {Object item = null;int headerSize = getHeaderSize();int mainSize = getItems().size();ItemViewBinder binder = getTypePool().getItemViewBinder(holder.getItemViewType());if (mHeader != null) {if (mHeader.isInRange(getItemCount(), position)) {item = mHeader.getItem(position);}}if (mFooter != null) {if (mFooter.isInRange(getItemCount(), position)) {int relativePos = position - headerSize - mainSize;item = mFooter.getItem(relativePos);}}if (item != null) {binder.onBindViewHolder(holder, item);return;}super.onBindViewHolder(holder, position - headerSize, payloads);}
复制代码

onBindViewHoldergetItemViewType的实现思想类似,判断是否是 Header/Footer 拿到相应的实体类,然后进行绑定。整个流程就是这样,当然别忘了也要在 getItemCount方法中将我们的 Header 与 Footer 的数量加进入,如:

@Override
public final int getItemCount() {int extensionSize = getHeaderSize() + getFooterSize();return super.getItemCount() + extensionSize;
}
复制代码

这样的封装可以让我们的 Header/Footer 里面的数据集与原本的数据集分离,我们的主数据再怎么增删查改都不会影响到Header/Footer 的正确性。

这样的实现目前有个比较蛋疼的点,我们调用ViewHoldergetAdapterPosition()时候会返回实际的 position,即包含了 Header 的数量,目前这点还没解决,需要手动把该 position 减去 Header 的数量才能得到原始数据集的相对位置。

以上,就完成了本次的小封装,赶紧去代码中实战吧。

基于 Multitype 开源库封装更好用的RecyclerView.Adapter相关推荐

  1. 基于第三方开源库的OPC服务器开发指南(2)——LightOPC的编译及部署

    基于第三方开源库的OPC服务器开发指南(2)--LightOPC的编译及部署 前文已经说过,OPC基于微软的DCOM技术,所以开发OPC服务器我们要做的事情就是开发一个基于DCOM的EXE文件.一个代 ...

  2. libcurl开源库封装ftp工具,支持多线程并发、断点续传、超时连接、传输速率控制

    各位朋友好,第一次在CSDN写博客,后续时间充足的话会陆续更新一些资源,大家一起探讨交流,感谢!!! 如有任何疑问,可以留言. 目的:再次封装CURL接口,使用FTP相关接口更方便,更易懂 功能: 可 ...

  3. 【barcode】 基于Jbarcode开源库生成条形码,提供添加备注信息的解决方案

    上一篇使用google的barcode4开源库生成条码,效果还是不错的,但是由于前几天leader有个需求,条码下面要添加备注信息- 当然解决方案也可以生成两个图片拼接在一起,但是觉得不太方便,就查了 ...

  4. 无信息变量选择(UVE)波长筛选算法--基于OpenSA开源库实现

    系列文章目录 "光晰本质,谱见不同",光谱作为物质的指纹,被广泛应用于成分分析中.伴随微型光谱仪/光谱成像仪的发展与普及,基于光谱的分析技术将不只停留于工业和实验室,即将走入生活, ...

  5. IFC模型文件查看器(基于IFC++开源库实现)

    关于IFC IFC是由buildingSMART以工业的产品资料交换标准STEP编号ISO-10303-11的产品模型信息描述用EXPERSS语言为基础,基于BIM中AEC/FM相关领域信息交流所指定 ...

  6. 基于 C++ POCO 库封装的异步多线程的 CHttpClient 类

    用惯了 Jetty 的 基于事件的 HttpClient 类,在C++平台上也没找到这样调用方式的类库,只好自己写一个了. 目前版本 1.0,朋友们看了给点建议.(注:Kylindai原创,转载请注明 ...

  7. C++ 使用libwebsockets开源库封装client类

    本文参考:封装利用libwebsockets写出的客户端.服务端程序为客户端服务端类_逍遥游的博客-CSDN博客_libwebsockets封装 最近项目需要使用C++连接websocket服务器,选 ...

  8. 涂抹去水印(基于lama-cleaner开源库)

    github连接:https://github.com/Sanster/lama-cleaner 今天发现了好玩的,拖动就可以去除水印,下边是原图和修改后的 安装lama-cleaner库 在你的环境 ...

  9. NLP - 微信好友个性签名情感分析( 基于Python开源库snownlp )

    配置与简介:https://blog.csdn.net/qq_42292831/article/details/88932177 本文源码下载:https://github.com/Hirehop/P ...

最新文章

  1. 西湖大学生命科学学院杨剑教授实验室招聘启事
  2. 中国软件业真的到了该反思的时候了
  3. cocos2d_x_03_经常使用类的使用_事件_画图
  4. 第二十八条:利用有限制通配符来提升API的灵活性
  5. html使用xml数据岛,html中的xml数据岛记录编辑与添加_xml技巧
  6. Map-Reduce入门
  7. 什么是爱?什么是幸福?
  8. CSS3的box-shadow属性:给指定的区域加阴影
  9. 【LeetCode】剑指 Offer 55 - I. 二叉树的深度
  10. python读取日志错误信息_使用Python将Exception异常错误堆栈信息写入日志文件
  11. oracle 日期型函数转换,oracle中,日期转换函数
  12. Nginx网络压缩 CSS压缩 图片压缩 JSON压缩
  13. Python 智能银行卡识别系统的实现 (2)—系统的实现
  14. Tensorflow 中文语音识别
  15. 开课吧JAVA高级架构师怎么样_开课吧JavaEE企业级高级架构师
  16. linux sd卡 分区变大,Linux下使用fdisk命令将高容量SD卡(SDHC)格成两个分区
  17. Ayla艾拉物联基于AWS构建IoT艾拉云
  18. RFNet:基于RGB-D数据的语义分割和意外障碍物检测的实时融合网络
  19. Java实现 LeetCode 292 Nim游戏
  20. 【c++基础】ifstream、istringstream的示例应用

热门文章

  1. 全国计算机组装与维修中级工,计算机维修工职业标准
  2. 7.Bean的自动装配
  3. SMMU架构手册之数据结构和转换流程(3)
  4. Panda3D 获取鼠标位置、Panda3D任务管理器
  5. 说说NetSuite中国合作代理商怎么样及有哪些优缺点
  6. bilibili账号申诉中心_关于b站的账号找回申诉机制
  7. umpc--赶有钱了买个这玩意
  8. VFP视频 高调更新
  9. xShell图形界面无法使用鼠标
  10. HTML中华传统文化题材网页《中国民间年画》HTML+CSS+JavaScript