পুনর্ব্যবহারযোগ্য ভিউতে ডেটা রিফ্রেশ করা এবং এর স্ক্রোলের অবস্থান বজায় রাখা


98

কীভাবে একজন প্রদর্শিত ডেটা রিফ্রেশ করে RecyclerView( notifyDataSetChangedতার অ্যাডাপ্টারে ফোন করে) এবং স্ক্রোলের অবস্থানটি যেখানে ছিল ঠিক সেখানে পুনরায় সেট করার বিষয়টি নিশ্চিত করে?

ভাল অ্যাল'এর ক্ষেত্রে ListViewএটি যা লাগে তা হ'ল পুনরুদ্ধার করা getChildAt(0), এটি পরীক্ষা করা getTop()এবং setSelectionFromTopপরে একই সঠিক ডেটা সহ কল করা।

এটি ক্ষেত্রে সম্ভব হবে বলে মনে হয় না RecyclerView

আমি অনুমান করি LayoutManagerযে এটি সত্যিকার অর্থে সরবরাহ করে এটি ব্যবহার করার কথা scrollToPositionWithOffset(int position, int offset), তবে অবস্থান এবং অফসেটটি পুনরুদ্ধার করার সঠিক উপায় কী?

layoutManager.findFirstVisibleItemPosition()এবং layoutManager.getChildAt(0).getTop()?

বা কাজটি সম্পন্ন করার জন্য আরও কি মার্জিত উপায় আছে?


এটি আপনার পুনর্ব্যবহারযোগ্য ভিউ বা লেআউটম্যানেজারের প্রস্থ এবং উচ্চতার মান সম্পর্কে। : এই সমাধান চেক করুন stackoverflow.com/questions/28993640/...
serefakyuz

@ কোনার্ড, আমার ক্ষেত্রে আমার কাছে সিকবার বাস্তবায়ন এবং নীচের ইস্যুটি সহ চলমান অডিও ফাইলগুলির তালিকা রয়েছে: ১) কয়েকটি শেষ সূচীকরণ আইটেমের সাথে সাথে এটি বিজ্ঞপ্তিপ্রাপ্ত আইটেমচেন্জড (পোস্ট) চালু করেছে এটি নীচে স্বয়ংক্রিয়ভাবে ভিউটিকে ধাক্কা দেয়। 2) notifyItemChanged (পোজ) এ থাকাকালীন এটি তালিকায় আটকে যায় এবং সহজে স্ক্রোল করার অনুমতি দেয় না। যদিও আমি একটি প্রশ্ন হিসাবে জিজ্ঞাসা করব তবে আপনি দয়া করে আমাকে জানান দয়া করে আপনি যদি এই জাতীয় সমস্যা সমাধানের জন্য আরও ভাল কোনও পন্থা পান।
CoDe

উত্তর:


146

আমি এটি ব্যবহার করি ^ _ ^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

এটি সহজ, আশা করি এটি আপনাকে সহায়তা করবে!


4
এটি আসলে সঠিক উত্তর ইমো। এটি কেবলমাত্র অ্যাসিনক্রোনাস ডেটা লোড হওয়ার কারণে জটিল হয়ে ওঠে তবে আপনি পুনরুদ্ধারটিকে পিছিয়ে দিতে পারেন।
এপিকপান্ডাফোরস

নিখুঁত +2 আমি যা খুঁজছিলাম তা হ'ল
অ্যাডেল টার্ক

@ বুবল্ট্রি পেস্ট.অফকোড.আর.এইডজেএসএলকিবিএলসিকিউএক্সএক্সপিবিপিইকিউ 4 মি আমি এটি বাস্তবায়নের চেষ্টা করছি কিন্তু এটি কাজ করছে না, আমি কী ভুল করছি?
কৌস্তুভ ভাগবত

@ কৌস্তুভ ভাগওয়াত সম্ভবত এটি ব্যবহারের আগে আপনার "রিসাইক্লারভিউস্টেট! = নাল" পরীক্ষা করা উচিত।
ডনইউ

4
এই কোডটি আমার ঠিক কোথায় ব্যবহার করা উচিত? অন ​​রিসুম পদ্ধতিতে?
ভাগ্যবান_জাগল

47

আমার বেশ অনুরূপ সমস্যা আছে। এবং আমি নিম্নলিখিত সমাধান নিয়ে এসেছি।

ব্যবহার notifyDataSetChangedকরা একটি খারাপ ধারণা। আপনার আরও নির্দিষ্ট হওয়া উচিত, তারপরে RecyclerViewআপনার জন্য স্ক্রোলের অবস্থা সংরক্ষণ করবে।

উদাহরণস্বরূপ, আপনার যদি কেবল রিফ্রেশ দরকার হয় বা অন্য কথায়, আপনি প্রতিটি দর্শন পুনরায় প্রত্যাবর্তন করতে চান, কেবল এটি করুন:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());

4
আমি অ্যাডাপ্টারের চেষ্টা করেছিলাম.নোটাইফাই আইটেমরেঞ্জ চ্যাঞ্জড (0, অ্যাডাপ্টার.জেট আইটেমকাউন্ট ()); সেও .notifyDataSetChanged ()
মণি

4
এটি আমার ক্ষেত্রে সহায়তা করেছে। আমি 0 পজিশনে বেশ কয়েকটি আইটেম সন্নিবেশ করছিলাম এবং notifyDataSetChangedস্ক্রোলের অবস্থানের সাথে নতুন আইটেম দেখানো পরিবর্তন হবে। যাইহোক, ব্যবহার করেন স্ক্রল রাষ্ট্র রাখে। notifyItemRangeInserted(0, newItems.size() - 1)RecyclerView
কার্লোস্ফ

খুব খুব ভাল উত্তর, আমি উত্তরটি পুরো একদিন অনুসন্ধান করি। আপনাকে অনেক অনেক ধন্যবাদ ..
গুন্ডু বান্দগার

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); সর্বশেষ গুগল আই / ও-তে নির্দেশিত হিসাবে এড়ানো উচিত: (পুনর্বিবেচনার উপস্থাপনাটি আউট এবং আউটস ইন দেখুন), রিসাইক্লিউরভিউকে ফ্ল্যাট ক্যাচ-সব রিফ্রেশের পরিবর্তে ঠিক কী আইটেমগুলি বদলেছে তা বলাই ভাল (যদি আপনার জ্ঞান থাকে) সব মতামত।
এ। স্টেইনবার্গেন

4
এটি কাজ করছে না, রিসাইক্লিউরভিউ রিফ্রেশিং শীর্ষে থাকা সত্ত্বেও আমি যুক্ত করেছি
Shaahul

21

সম্পাদনা: ঠিক একই আপাত অবস্থানটি পুনরুদ্ধার করতে , যেমনটি হয়েছিল ঠিক তেমন দেখতে, আমাদের কিছুটা আলাদা করতে হবে (সঠিক স্ক্রোলওয়াইয়ের মানটি পুনরুদ্ধার করার জন্য নীচে দেখুন):

অবস্থানটি সংরক্ষণ করুন এবং এটি অফসেট করুন:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();

outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);

এবং তারপরে স্ক্রোলটি পুনরুদ্ধার করুন:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

এটি তালিকাটিকে তার সঠিক আপাত অবস্থানে পুনরুদ্ধার করে । দৃশ্যত কারণ এটি ব্যবহারকারীর কাছে একই দেখাবে, তবে এর একই স্ক্রোলওয়াইয়ের মান থাকবে না (ল্যান্ডস্কেপ / প্রতিকৃতি বিন্যাসের মাত্রায় সম্ভাব্য পার্থক্যের কারণে)।

মনে রাখবেন এটি কেবল লিনিয়ারলআউটআমনেজারের সাথে কাজ করে।

--- সঠিক স্ক্রোলওয়াই কীভাবে পুনরুদ্ধার করবেন তার নীচে, যা সম্ভবত তালিকাটি অন্যরকম দেখাচ্ছে ---

  1. এর মতো একটি অনস্ক্রোললিস্টার প্রয়োগ করুন:

    private int mScrollY;
    private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrollY += dy;
        }
    };
    

এটি mScrolY এ সর্বদা সঠিক স্ক্রোল অবস্থান সংরক্ষণ করবে।

  1. আপনার বান্ডলে এই ভেরিয়েবলটি সংরক্ষণ করুন এবং এটিকে রাষ্ট্রের পুনরুদ্ধারে একটি ভিন্ন ভেরিয়েবলে পুনরুদ্ধার করুন , আমরা এটিকে এমস্টেটস্ক্রোলই বলব।

  2. রাষ্ট্র পুনরুদ্ধারের পরে এবং আপনার পুনর্ব্যবহারযোগ্য ভিউ এর সাথে এর সমস্ত ডেটা পুনরায় সেট করেছে:

    mRecyclerView.scrollBy(0, mStateScrollY);
    

এটাই.

সাবধান, আপনি যে স্ক্রোলটিকে অন্য কোনও ভেরিয়েবলে পুনরুদ্ধার করেছেন, এটি গুরুত্বপূর্ণ, কারণ অনস্ক্রোললিস্টারকে .scrolBy () দিয়ে ডাকা হবে এবং পরবর্তীকালে এমস্টেটস্ক্রোলওয়াইতে সঞ্চিত মানটিতে এমস্ক্রোলওয়াই সেট করবে। আপনি যদি এটি না করেন তবে স্ক্রোলওয়াইয়ের দ্বিগুণ স্ক্রোল মান হবে (কারণ অনস্ক্রোললিস্টনার ডেল্টাসের সাথে কাজ করে, পরম স্ক্রোলগুলির সাথে নয়)।

ক্রিয়াকলাপগুলিতে রাষ্ট্রীয় সঞ্চয় এইভাবে অর্জন করা যায়:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

এবং আপনার অনক্রিটে () এ কলটি পুনরুদ্ধার করতে:

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

খণ্ডগুলিতে রাষ্ট্রীয় সঞ্চয় একইভাবে কাজ করে তবে প্রকৃত রাষ্ট্রীয় সঞ্চয় করতে কিছুটা অতিরিক্ত কাজ প্রয়োজন, তবে এর সাথে প্রচুর নিবন্ধ রয়েছে, সুতরাং কীভাবে স্ক্রোলটি সংরক্ষণের নীতিগুলি খুঁজে পেতে আপনার সমস্যা হওয়া উচিত নয় এবং এটি পুনরুদ্ধার একই থাকে।


আমি বুঝতে পারি না আপনি পুরো উদাহরণ পোস্ট করতে পারেন?

এটি আসলে একটি সম্পূর্ণ উদাহরণের কাছাকাছি যা আপনি পেতে পারেন যদি আপনি আমার সম্পূর্ণ ক্রিয়াকলাপ পোস্ট করতে না চান
এ। স্টেনবার্গেন

আমি কিছু পাই না। তালিকার শুরুতে যদি যোগ করা আইটেমগুলি থাকে, তবে একই স্ক্রোলিং অবস্থানে থাকা কি ভুল হবে না, কারণ আপনি এখন এই অঞ্চলটি গ্রহণ করেছেন এমন বিভিন্ন আইটেমের দিকে নজর রাখবেন?
অ্যান্ড্রয়েড বিকাশকারী

হ্যাঁ, সেক্ষেত্রে আপনাকে পুনরুদ্ধার করার সময় আপনার অবস্থানটি সামঞ্জস্য করতে হবে, তবে এটি কোনও প্রশ্নের অংশ নয়, সাধারণত ঘোরানোর সময় আপনি আইটেমগুলি যোগ বা সরিয়ে না যান। এটি অদ্ভুত এবং আচরণ হতে পারে এবং সম্ভবত উপস্থিত কিছু বিশেষ ক্ষেত্রে বাদে ব্যবহারকারীর স্বার্থে নয়, যা আমি সত্যি বলতে পারি না।
এ। স্টেইনবার্গেন

9

হ্যাঁ আপনি কেবল অ্যাডাপ্টারের কনস্ট্রাক্টর তৈরি করে এই সমস্যাটি সমাধান করতে পারেন, আমি এখানে কোডিং অংশটি ব্যাখ্যা করছি:

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

এখন আপনি দেখতে পাচ্ছেন যে আমি অ্যাডাপ্টারটি নাল কিনা তা পরীক্ষা করে দেখেছি এবং এটি শূন্য হলেই আরম্ভ করব।

যদি অ্যাডাপ্টারটি নাল না থাকে তবে আমি নিশ্চিত হয়েছি যে আমি আমার অ্যাডাপ্টারটি কমপক্ষে একবারে শুরু করেছি।

সুতরাং আমি কেবল অ্যাডাপ্টারে তালিকা যুক্ত করব এবং নোটিফাইড্যাটাসেটচেঞ্জড কল করব।

রিসাইক্লারভিউ সর্বদা স্ক্রলড শেষ অবস্থান ধরে রাখে, অতএব আপনাকে শেষ অবস্থান সংরক্ষণ করতে হবে না, কেবলমাত্র নোটিফাইড্যাটাসেটচেঞ্জড কল করুন, রিসাইক্লার ভিউ সর্বদা শীর্ষে না গিয়ে ডেটা রিফ্রেশ করে।

ধন্যবাদ শুভ কোডিং


আমি মনে করি এটি গৃহীত উত্তর @ কনরাড মোরাওস্কি
জিতিন জুড

6

এইভাবে মোড়ানোর জন্য @ ডনইউ উত্তরটি ব্যবহার করে স্ক্রোল অবস্থান রাখুন notifyDataSetChanged():

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)

নিখুঁত ... সেরা উত্তর
jlandyr

আমি দীর্ঘদিন ধরে এই উত্তরটি খুঁজছি। আপনাকে অনেক ধন্যবাদ.
আলা আবুজারিফা

2

@ ডনইউয়ের শীর্ষের উত্তরটি কাজ করে তবে রিসাইক্লারভিউ প্রথমে শীর্ষে স্ক্রোল করবে, তারপরে উদ্দেশ্যযুক্ত স্ক্রোল অবস্থানে ফিরে যাবে যা "ঝাঁকুনির মতো" প্রতিক্রিয়া সৃষ্টি করে যা আনন্দদায়ক নয়।

পুনর্ব্যবহারযোগ্য ভিউ রিফ্রেশ করার জন্য, বিশেষ করে অন্য ক্রিয়াকলাপ থেকে এসে ঝাঁকুনি না দিয়ে এবং স্ক্রোলের অবস্থান বজায় রাখার পরে, আপনাকে নিম্নলিখিতগুলি করা দরকার।

  1. নিশ্চিত করুন যে আপনি ডিফুটিল ব্যবহার করে আপনাকে পুনর্ব্যবহারযোগ্য ভিউ আপডেট করছেন। সে সম্পর্কে এখানে আরও পড়ুন: https://www.jorterdev.com/20873/android-recyclerview-diffutil
  2. আপনার ক্রিয়াকলাপের সূত্রপাত, বা আপনি যে ক্রিয়াকলাপ আপডেট করতে চান সেই মুহুর্তে আপনার রিসাইক্লারভিউতে ডেটা লোড করুন। বিচ্ছিন্ন ব্যবহার করে, অবস্থানটি বজায় রেখে কেবল আপডেটগুলি পুনর্ব্যবহারযোগ্য পর্যবেক্ষণে করা হবে।

আশাকরি এটা সাহায্য করবে.


1

আমি রিসাইক্লারভিউ ব্যবহার করি নি তবে তালিকাভিউতে এটি করেছি। পুনর্ব্যবহারযোগ্য নমুনা কোড:

setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        rowPos = mLayoutManager.findFirstVisibleItemPosition();

ব্যবহারকারী যখন স্ক্রোল করছে তখন শ্রোতা। কর্মক্ষমতা ওভারহেড তাৎপর্যপূর্ণ নয়। এবং প্রথম দৃশ্যমান অবস্থানটি এইভাবে সঠিক।


ঠিক আছে, findFirstVisibleItemPosition"প্রথম দৃশ্যমান দৃশ্যের অ্যাডাপ্টারের অবস্থান" প্রদান করে তবে অফসেটটি কী।
কনরাড মোরাউস্কি

4
দৃশ্যমান পজিশনের জন্য তালিকাভিউয়ের নির্বাচন পদ্ধতিটি পুনঃনির্ধারণের জন্য কি একই জাতীয় পদ্ধতি রয়েছে? আমি তালিকাভিউয়ের জন্য এটি করেছি।
আসল অ্যান্ড্রয়েড

4
ডাই প্যারামিটারের সাহায্যে সেটস্ক্রোলওয়াই পদ্ধতি কীভাবে ব্যবহার করা যায়, যা আপনি সংরক্ষণ করবেন? যদি এটি কাজ করে তবে আমি আমার উত্তর আপডেট করব। আমি রিসাইক্লারভিউ ব্যবহার করি নি তবে আমার ভবিষ্যতের অ্যাপ্লিকেশনটিতে চাই।
আসল অ্যান্ড্রয়েড

স্ক্রোল অবস্থানটি কীভাবে সংরক্ষণ করতে হবে দয়া করে নীচে আমার উত্তরটি দেখুন।
এ। স্টেইনবার্গেন 12'15

4
ঠিক আছে, আমি পরের বার এটি মনে রাখব, আমি আপনাকে দূষিত করে তুলিনি। তবে আমি সংক্ষিপ্ত এবং অসম্পূর্ণ উত্তরগুলির সাথে একমত নই কারণ আমি যখন ছোট বিকাশগুলি শুরু করলাম তখন অন্যান্য বিকাশকারীরা প্রচুর পটভূমি তথ্যটি সত্যই আমাকে সহায়তা করতে পারেনি কারণ তারা প্রায়শই অসম্পূর্ণ ছিল এবং আমাকে অনেকগুলি স্পষ্ট করে প্রশ্ন জিজ্ঞাসা করতে হয়েছিল, এমন কিছু আপনি সাধারণত পুরানো স্ট্যাকওভারফ্লো পোস্টগুলিতে করতে পারে না। এছাড়াও লক্ষ করুন কীভাবে কোনরাদ কোনও উত্তর গ্রহণ করেন নি, যা ইঙ্গিত দেয় যে এই উত্তরগুলি তার সমস্যার সমাধান করেনি। যদিও আমি আপনার সাধারণ বিষয় দেখতে।
এ। স্টেইনবার্গেন

-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

এখানে সমাধানটি হ'ল নতুন বার্তা এলে স্ক্রোলিং পুনর্ব্যবহারযোগ্য পর্যালোচনা করা।

অন-চেঞ্জড () পদ্ধতি পুনর্ব্যবহারযোগ্য পর্যবেক্ষণে সম্পাদিত ক্রিয়াটি সনাক্ত করে।


-1

কোটলিনে এটি আমার জন্য কাজ করছে।

  1. অ্যাডাপ্টার তৈরি করুন এবং কনস্ট্রাক্টারে আপনার ডেটা হস্তান্তর করুন
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. এই সম্পত্তিটি পরিবর্তন করুন এবং নোটিফিকেশন ডেটাসেটচ্যাঞ্জড ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

স্ক্রোল অফসেট পরিবর্তন হয় না।


-2

আইটেমগুলির একটি তালিকাতে আমার এই সমস্যাটি ছিল যা প্রত্যেকের মিনিটে একটি সময় ছিল যতক্ষণ না তারা 'যথাযথ' হয় এবং আপডেট করার প্রয়োজন হয়। আমি ডেটা আপডেট করব এবং তারপরে কল করব

orderAdapter.notifyDataSetChanged();

এবং এটি প্রতিবার শীর্ষে স্ক্রোল করতাম। আমি যে প্রতিস্থাপন

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

এবং এটা ঠিক ছিল। এই থ্রেডের অন্য কোনও পদ্ধতিই আমার পক্ষে কাজ করে নি। যদিও এই পদ্ধতিটি ব্যবহার করার সময়, এটি আপডেট হওয়ার সাথে সাথে এটি প্রতিটি স্বতন্ত্র আইটেম ফ্ল্যাশ করে তোলে তাই আমাকেও এটি প্যারেন্ট টুকরাটির অনক্রিটভিউতে রেখে দিতে হয়েছিল

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }

-2

আপনার যদি একটি পুনর্ব্যবহারযোগ্য আইটেমের ভিতরে এক বা একাধিক সম্পাদনাপৃষ্ঠাগুলি থাকে, তবে এই কনফিগারেশনটিকে পুনর্ব্যবহারযোগ্য পিতামাতার দৃষ্টিতে রেখে যান:

android:focusable="true"
android:focusableInTouchMode="true"

আমার যখন এই সমস্যাটি তখনই শুরু হয়েছিল যখন আমি পুনর্ব্যবহারযোগ্য আইটেমটি থেকে আর একটি ক্রিয়াকলাপ শুরু করেছি, যখন আমি ফিরে এসে একটি আইটেমের একটি ক্ষেত্রের একটি আপডেট বিজ্ঞপ্তি আইটেম চেঞ্জড (অবস্থান) আরভি মুভের স্ক্রোল দিয়ে সেট করেছিলাম এবং আমার উপসংহারটি হ'ল, সম্পাদনা পাঠের অটোফোকাস আইটেম, উপরের কোডটি আমার সমস্যার সমাধান করেছে।

সেরা


-3

পুরাতন অবস্থান এবং অবস্থান একই হলে কেবল ফিরে আসুন;

private int oldPosition = -1;

public void notifyItemSetChanged(int position, boolean hasDownloaded) {
    if (oldPosition == position) {
        return;
    }
    oldPosition = position;
    RLog.d(TAG, " notifyItemSetChanged :: " + position);
    DBMessageModel m = mMessages.get(position);
    m.setVideoHasDownloaded(hasDownloaded);
    notifyItemChanged(position, m);
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.